patterncsharpMinor
Custom HttpClient Wrapper
Viewed 0 times
customwrapperhttpclient
Problem
I need to wrap httpClient because I'm using a custom token provider. I will use this code with asp.net mvc to communicate with our webApi2 server. ( Using webApi2 with directly from ui with angularjs etc. off the case. )
I shared this code and asking your opinions because;
users)
Note: I implemented it with concerns of di. So I can wrap restSharp too.
```
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using MyLibrary.Core.Components.Json;
using MyLibrary.Core.Components.Json.Implementations;
namespace MyLibrary.HttpClientWrapper
{
public class ResourceServerRestClient : IResourceServerRestClient
{
private readonly ITokenProvider _tokenProvider;
private readonly IJsonManager _jsonManager;
public ResourceServerRestClient(ITokenProvider tokenProvider, IJsonManager jsonManager)
{
_tokenProvider = tokenProvider;
_jsonManager = jsonManager;
}
public string BaseAddress { get; set; }
public Task GetAsync(string uri, string clientId)
{
return CheckAndInvokeAsync(async token =>
{
using (var client = new HttpClient())
{
ConfigurateHttpClient(client, token, clientId);
HttpResponseMessage response = await client.GetAsync(uri).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsAsync();
}
var exception = new Exception($"Resource server returned an error. Statu
I shared this code and asking your opinions because;
- I don't want any deadlock to occur.
- I see code reuse which is bad (but don't know how to solve).
- Maybe throwing exceptions somehow different and correct way.
- It supposed to work under huge load (150K realtime requests from
users)
Note: I implemented it with concerns of di. So I can wrap restSharp too.
```
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using MyLibrary.Core.Components.Json;
using MyLibrary.Core.Components.Json.Implementations;
namespace MyLibrary.HttpClientWrapper
{
public class ResourceServerRestClient : IResourceServerRestClient
{
private readonly ITokenProvider _tokenProvider;
private readonly IJsonManager _jsonManager;
public ResourceServerRestClient(ITokenProvider tokenProvider, IJsonManager jsonManager)
{
_tokenProvider = tokenProvider;
_jsonManager = jsonManager;
}
public string BaseAddress { get; set; }
public Task GetAsync(string uri, string clientId)
{
return CheckAndInvokeAsync(async token =>
{
using (var client = new HttpClient())
{
ConfigurateHttpClient(client, token, clientId);
HttpResponseMessage response = await client.GetAsync(uri).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsAsync();
}
var exception = new Exception($"Resource server returned an error. Statu
Solution
Here is a one way to do it
I hope it's help..
public class ResourceServerRestClient : IResourceServerRestClient
{
private readonly ITokenProvider _tokenProvider;
private readonly IJsonManager _jsonManager;
private HttpClient _client;
// you can inject the interfaces
public ResourceServerRestClient(ITokenProvider tokenProvider, IJsonManager jsonManager)
{
_tokenProvider = tokenProvider;
_jsonManager = jsonManager;
}
// who set this property?
public string BaseAddress { get; set; }
// this is just to demonstrate a simple reuse technique. you can do it in other ways. (singleton, DI, static)
public HttpClient Client => _client ?? (_client = new HttpClient());
public Task GetAsync(string uri, string clientId)
{
return InvokeAsync(
clientId,
client => client.GetAsync(uri),
response => response.Content.ReadAsAsync());
}
public Task PostAsJsonAsync(object data, string uri, string clientId)
{
return InvokeAsync(
clientId,
client => client.PostAsJsonAsync(uri, data),
response => response.Content.ReadAsAsync());
}
public Task PostAsJsonAsync(object data, string uri, string clientId)
{
return InvokeAsync(
clientId,
client => client.PostAsJsonAsync(uri, data));
}
public Task PutAsJsonAsync(object data, string uri, string clientId)
{
return InvokeAsync(
clientId,
client => client.PutAsJsonAsync(uri, data));
}
public Task PutAsJsonAsync(object data, string uri, string clientId)
{
return InvokeAsync(
clientId,
client => client.PutAsJsonAsync(uri, data),
response => response.Content.ReadAsAsync());
}
private async Task InvokeAsync(
string clientId,
Func> operation,
Func> actionOnResponse = null)
{
if(operation == null)
throw new ArgumentNullException(nameof(operation));
// consider to make pre check validation also to clientId argument if it's needed
var token = GetToken();
ConfigurateHttpClient(_client, token, clientId);
HttpResponseMessage response = await operation(_client).ConfigureAwait(false);
if(!response.IsSuccessStatusCode)
{
var exception = new Exception($"Resource server returned an error. StatusCode : {response.StatusCode}");
exception.Data.Add("StatusCode", response.StatusCode);
throw exception;
}
if(actionOnResponse != null)
{
return await actionOnResponse(response).ConfigureAwait(false);
}
else
{
return default(T);
}
}
private string GetToken()
{
// if IsTokenNullOrExpired return null and not string.Empty, you can do the foloowing:
var token = await _tokenProvider.IsTokenNullOrExpired() ?? await _tokenProvider.GetTokenAsync();
if(string.IsNullOrEmpty(token))
{
var exception = new Exception();
exception.Data.Add("StatusCode", HttpStatusCode.Unauthorized);
throw exception;
}
//else, do this:
string token = await _tokenProvider.IsTokenNullOrExpired();
if(string.IsNullOrEmpty(token))
{
token = await _tokenProvider.GetTokenAsync();
if(string.IsNullOrEmpty(token))
{
var exception = new Exception();
exception.Data.Add("StatusCode", HttpStatusCode.Unauthorized);
throw exception;
}
}
return token;
}
private void ConfigurateHttpClient(HttpClient client, string bearerToken, string resourceServiceClientName)
{
// do this first
if(string.IsNullOrEmpty(BaseAddress))
{
throw new Exception("BaseAddress is required!");
}
// consider to do pre check also for arguments if it make sense
if(!string.IsNullOrEmpty(resourceServiceClientName))
{
client.DefaultRequestHeaders.Add("CN", resourceServiceClientName);
}
client.BaseAddress = new Uri(BaseAddress);
client.Timeout = new TimeSpan(0, 0, 0, 10);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
}
}I hope it's help..
Code Snippets
public class ResourceServerRestClient : IResourceServerRestClient
{
private readonly ITokenProvider _tokenProvider;
private readonly IJsonManager _jsonManager;
private HttpClient _client;
// you can inject the interfaces
public ResourceServerRestClient(ITokenProvider tokenProvider, IJsonManager jsonManager)
{
_tokenProvider = tokenProvider;
_jsonManager = jsonManager;
}
// who set this property?
public string BaseAddress { get; set; }
// this is just to demonstrate a simple reuse technique. you can do it in other ways. (singleton, DI, static)
public HttpClient Client => _client ?? (_client = new HttpClient());
public Task<T> GetAsync<T>(string uri, string clientId)
{
return InvokeAsync<T>(
clientId,
client => client.GetAsync(uri),
response => response.Content.ReadAsAsync<T>());
}
public Task<T> PostAsJsonAsync<T>(object data, string uri, string clientId)
{
return InvokeAsync<T>(
clientId,
client => client.PostAsJsonAsync(uri, data),
response => response.Content.ReadAsAsync<T>());
}
public Task PostAsJsonAsync(object data, string uri, string clientId)
{
return InvokeAsync<object>(
clientId,
client => client.PostAsJsonAsync(uri, data));
}
public Task PutAsJsonAsync(object data, string uri, string clientId)
{
return InvokeAsync<object>(
clientId,
client => client.PutAsJsonAsync(uri, data));
}
public Task<T> PutAsJsonAsync<T>(object data, string uri, string clientId)
{
return InvokeAsync<T>(
clientId,
client => client.PutAsJsonAsync(uri, data),
response => response.Content.ReadAsAsync<T>());
}
private async Task<T> InvokeAsync<T>(
string clientId,
Func<HttpClient, Task<HttpResponseMessage>> operation,
Func<HttpResponseMessage, Task<T>> actionOnResponse = null)
{
if(operation == null)
throw new ArgumentNullException(nameof(operation));
// consider to make pre check validation also to clientId argument if it's needed
var token = GetToken();
ConfigurateHttpClient(_client, token, clientId);
HttpResponseMessage response = await operation(_client).ConfigureAwait(false);
if(!response.IsSuccessStatusCode)
{
var exception = new Exception($"Resource server returned an error. StatusCode : {response.StatusCode}");
exception.Data.Add("StatusCode", response.StatusCode);
throw exception;
}
if(actionOnResponse != null)
{
return await actionOnResponse(response).ConfigureAwait(false);
}
else
{
return default(T);
}
}
private string GetToken()
{
// if IsTokenNullOrExpired return null and not string.Empty, you can do the Context
StackExchange Code Review Q#124155, answer score: 2
Revisions (0)
No revisions yet.