HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Custom HttpClient Wrapper

Submitted by: @import:stackexchange-codereview··
0
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;

  • 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

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.