rynowak / httpclientfactorydemos Goto Github PK
View Code? Open in Web Editor NEWDemos for aspnet/HttpClientFactory
License: MIT License
Demos for aspnet/HttpClientFactory
License: MIT License
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 { }
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.