Git Product home page Git Product logo

httpclientfactorydemos's People

Contributors

rynowak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

httpclientfactorydemos's Issues

Feedback On Strongly Typed HttpClients

Hey man, I realise i'm late to the show a bit here but I was just watching this on youtube ...
https://www.youtube.com/watch?v=Lb12ZtlyMPg&index=15&list=PL1rZQsJPBU2StolNg0aqvQswETPcYnNKL

...
I use WebAPI OData extensively and here's how I took HttpClient and extended it to get the desired functionality ...

using log4net;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Configuration;
using System.Web.SessionState;

namespace Core.Objects
{
    public static class HttpClientExtensions
    {
        static readonly ILog log = LogManager.GetLogger(typeof(HttpClient));


        /// <summary>
        /// Signs and sends a http request
        /// </summary>
        /// <typeparam name="T">type of data being sent</typeparam>
        /// <param name="client">client to use to send</param>
        /// <param name="query">where to send it</param>
        /// <param name="data">object to send</param>
        /// <param name="signature">signature</param>
        /// <returns></returns>
        public static async Task<TResult> PostAsJsonAsync<T, TResult>(this HttpClient client, string query, T data, string signature = null)
        {
            if (signature != null) client.DefaultRequestHeaders.Add("signature", signature);
            var result = await client.PostAsJsonAsync(query, data);
            if (signature != null) client.DefaultRequestHeaders.Remove("signature");
            return await result.Content.ReadAsAsync<TResult>();
        }

        public static async Task<ICollection<TResult>> PostAndRetrieveODataCollectionAsync<T, TResult>(this HttpClient client, string query, T data, string signature = null)
        {
            var result = await client.PostAsJsonAsync<T, ODataCollection<TResult>>(query, data, signature);

            return result.Value;
        }

        public static async Task<T> Get<T>(this HttpClient client, string query)
        {
            HttpResponseMessage response = null;
            try
            {
                response = await client.GetAsync(query);
                return await response.Content.ReadAsAsync<T>();
            }
            catch (Exception ex)
            {
                log.Error("Problem querying " + query);
                log.Error(ex);
                if (response != null)
                {
                    var apiException = await response.Content.ReadAsStringAsync();
                    var err = new Exception(apiException, ex);
                    log.Error(err);
                    throw err;
                }
                throw ex;
            }
        }

        public static async Task<T> GetFromOData<T>(this HttpClient client, string query)
        {
            HttpResponseMessage response = null;
            try
            {
                response = await client.GetAsync(query);
                var result = await response.Content.ReadAsAsync<ODataResult<T>>();
                return result.Value;
            }
            catch (Exception ex)
            {
                log.Error("Problem querying " + query);
                log.Error(ex);
                if (response != null)
                {
                    var apiException = await response.Content.ReadAsStringAsync();
                    var err = new Exception(apiException, ex);
                    log.Error(err);
                    throw err;
                }
                throw ex;
            }
        }
        
        public static async Task<ICollection<T>> GetODataCollection<T>(this HttpClient client, string query)
        {
            try
            {
                return (
                    await client.GetAsync(query)
                        .ContinueWith(t => t.Result.Content.ReadAsAsync<ODataCollection<T>>())
                        .Unwrap()
                    )
                    .Value;
            }
            catch (Exception)
            {
                log.Error("Problem querying " + query);
                throw;
            }
        }
       
        /// <summary>
        /// Adds authorization information to the client by making an auth call with the given credentials
        /// </summary>
        /// <param name="client">The HttpClient to attach the authorization information to</param>
        /// <param name="user">The username to use for authentication</param>
        /// <param name="pass">The password to use for authentication</param>
        /// <returns>An authenticated HttpClient</returns>
        public static async Task<JObject> Authenticate(this HttpClient client, string user, string pass)
        {
            var authRequest = await client.PostAsync("Authenticate", new StringContent("username=" + user + "&password=" + pass + "&grant_type=password"));
            var authResponse = await authRequest.Content.ReadAsStringAsync();

            if (!authResponse.StartsWith("<!DOCTYPE"))
            {
                dynamic token = JObject.Parse(authResponse);

                try {
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token.token_type.ToString(), token.access_token.ToString());
                    return token;
                }
                catch { /* if we get here the server returned a json repsonse but it wasn't a token, it was more than likely an auth failure. */ }
            }
            else log.Warn("Auth request Failed for user " + user);
            
            return null;
        }

        /// <summary>
        /// Adds the given collection of header values to an instance of a http client
        /// </summary>
        /// <param name="client">the http client</param>
        /// <param name="headers">the header values to add</param>
        /// <returns>HttpClient with the given header values</returns>
        public static HttpClient AddHeaders(this HttpClient client, NameValueCollection headers)
        {
            foreach (var key in headers.Keys)
                try { client.DefaultRequestHeaders.Add(key.ToString(), headers.Get(key.ToString())); } catch { }

            return client;
        }

        public static HttpClient UseBasicAuth(this HttpClient client, string user, string pass)
        {
            return client.UseBasicAuth(Convert.ToBase64String(Encoding.UTF8.GetBytes("username=" + user + "&password=" + pass + "&grant_type=password")));
        }

        public static HttpClient UseBasicAuth(this HttpClient client, string authString)
        {
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", authString);
            return client;
        }

        public static HttpClient UseBaseUrl(this HttpClient client, string url)
        {
            client.BaseAddress = new Uri(url);
            return client;
        }

        /// <summary>
        /// Adds authorization information from the given session to the given HttpClient
        /// </summary>
        /// <param name="client">The HttpClient to attach the authorization information to</param>
        /// <param name="request">The HttpSessionState from which to acquire the authorization information</param>
        /// <returns>An authenticated HttpClient</returns>
        public static HttpClient AddAuthFromSession(this HttpClient client, HttpSessionStateBase session)
        {
            if (client.DefaultRequestHeaders.Authorization == null && session != null)
            {
                var token = (JObject)session["token"];
                if(token != null)
                    client.DefaultRequestHeaders.Add("Authorization", "bearer " + token["access_token"].ToString());
            }
            return client;
        }

        /// <summary>
        /// Adds authorization information from the given session to the given HttpClient
        /// </summary>
        /// <param name="client">The HttpClient to attach the authorization information to</param>
        /// <param name="request">The HttpSessionState from which to acquire the authorization information</param>
        /// <returns>An authenticated HttpClient</returns>
        public static HttpClient AddAuthFromSession(this HttpClient client, HttpSessionState session)
        {
            if(client.DefaultRequestHeaders.Authorization == null && session != null)
            {
                var token = (JObject)session["token"];
                if (token != null)
                    client.DefaultRequestHeaders.Add("Authorization", "bearer " + token["access_token"].ToString());
            }
            return client;
        }

        /// <summary>
        /// Adds authorization information from the given request to the given HttpClient
        /// </summary>
        /// <param name="client">The HttpClient to attach the authorization information to</param>
        /// <param name="request">The HttpRequest from which to acquire the authorization information</param>
        /// <returns>An authenticated HttpClient</returns>
        public static HttpClient AddAuthFromRequest(this HttpClient client, HttpRequest request)
        {
            var auth = request.Headers["authorization"];
            if(auth != null && client.DefaultRequestHeaders.Authorization == null) client.DefaultRequestHeaders.Add("Authorization", auth);
            return client;
        }

        /// <summary>
        /// Sets the base URI on the given HttpClient instance to the one in config
        /// </summary>
        /// <param name="client">the HttpClient</param>
        /// <returns>The HttpClient (updated)</returns>
        public static HttpClient WithApiBaseUriFromConfig(this HttpClient client)
        {
            client.BaseAddress = new Uri(ConfigurationManager.AppSettings["ApiUrl"]);
            return client;
        }

        public static HttpClient AddAuthToken(this HttpClient client, string token)
        {
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
            return client;
        }

        /// <summary>
        /// Does a HttpGET and parses the repsonse in to the response type T
        /// </summary>
        /// <typeparam name="T">Type of the result</typeparam>
        /// <param name="client">the HttpClient to use</param>
        /// <param name="url">the relative url to the endpoint to call</param>
        /// <returns>The API's response result of type T</returns>
        public static Task<T> GetAsync<T>(this HttpClient client, string url)
        {
            return client.GetAsync(url)
                .ContinueWith(t => t.Result.Content.ReadAsAsync<T>())
                .Unwrap();
        }

        /// <summary>
        /// Determines the API base url (from config), token information (from session) and does a HttpGET
        /// and parses the repsonse in to the response type T
        /// </summary>
        /// <typeparam name="T">Type of the result</typeparam>
        /// <param name="client">the HttpClient to use</param>
        /// <param name="url">the relative url to the endpoint to call</param>
        /// <returns>The API's response result of type T</returns>
        public static Task<T> SecureGetAsync<T>(this HttpClient client, string url)
        {
            return client
                .WithApiBaseUriFromConfig()
                .AddAuthFromSession(HttpContext.Current?.Session)
                .GetAsync<T>(url);
        }
        
        public async static Task<HttpClient> ConfigureFromConfig(this HttpClient client)
        {
            if (ConfigurationManager.AppSettings["apiUrl"] != null)
                client = client.WithApiBaseUriFromConfig();

            if (ConfigurationManager.AppSettings["AppUser"] != null && ConfigurationManager.AppSettings["AppPass"] != null)
                await client.Authenticate(ConfigurationManager.AppSettings["AppUser"].ToString(), ConfigurationManager.AppSettings["AppPass"].ToString());

            return client;
        }

        /// <summary>
        /// Generate and attach an encrypted signature to client as header value to be sent in all requests going forward
        /// </summary>
        /// <param name="client"></param>
        /// <param name="signature"></param>
        /// <param name="crypto"></param>
        /// <returns></returns>
        public static HttpClient SignRequestsWith(this HttpClient client, Signature signature, ICrypto<Signature> crypto)
        {
            try
            {
                var encryptedSignature = crypto.Encrypt(signature, ((MachineKeySection)ConfigurationManager.GetSection("system.web/machineKey")).DecryptionKey);
                return client.SignRequestsWith(encryptedSignature);
            }
            catch (Exception ex)
            {
                log.Error("Failed adding signature to HttpClient", ex);
            }

            return client;
        }

        public static HttpClient SignRequestsWith(this HttpClient client, string signature)
        {
            if(signature != null) client.DefaultRequestHeaders.Add("signature", signature);
            return client;
        }

        /// <summary>
        /// remove previous signature from the given http client to stop signing future requests
        /// </summary>
        /// <param name="client"></param>
        /// <returns></returns>
        public static HttpClient RemoveRequestSignature(this HttpClient client)
        {
            client.DefaultRequestHeaders.Remove("signature");
            return client;
        }
    }
}

I'm using owin and Ninject for my DI and when I construct a HttpClient instance I always do the same way (via DI) like this ...

var client = new HttpClient(new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate })
                .WithApiBaseUriFromConfig()

... then ...
.AddAuthFromRequest(Request); // when in a HttpRequest Context
... or ...
.Authenticate(user, pass); // to add an auth header to all subsequent requests.

...
My thinking is that making HttpClient any smarter than "this is a REST service" or "this is an OData service" is probably going to result in chunks of business logic being held in the HttpClient.

Depending on your stance you may say that's a good thing but I have found that too much logic in the middle of complex stacks results in a key problem.

For example:

I have been building a multi tenancy based system that's a hybrid, CMS, DMS, other type "collection of systems" to serve our clients in a sort of middleware function, "think biztalk but as with a CMS" type thing.

I found that putting too many rules in my platform prevents users from using it in their specific scenarios.
And allowing my users to put logic in has to be done in a way that it doesn't affect the multi-tenancy "ness" of my platform.

So there's this balance that must be drawn.
That said ...

If I wanted to build something that's .Net based and talks to something like like VSTS or github ... it would be super cool to have a strongly typed API layer I can explore and having watched that video on youtube it's got me thinking of building a MyCompanyHttpClient.

Random question though ...
Why not use inheritance because naturally everything a HttpClient can do you can do to the Githhub service ... or is this a deliberate effort to "hide" the root level abstract functionality more general to HttpClient?

public class GitHubHttpClient : HttpClient { }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.