15

As part of an ASP.Net Core project that I am working on I have a requirement to communicate with a number of different Rest based API Endpoints from within my WebApi. To achieve this I am using a number of service classes that each instantiate a static HttpClient. Essentially I have a service class for each of the Rest based endpoints that the WebApi connects to.

An example of how the static HttpClient is instantiated in each of the service classes can be seen below.

private static HttpClient _client = new HttpClient()
{
    BaseAddress = new Uri("http://endpointurlexample"),            
};

Whilst the above is working well, it does not allow for effective unit testing of the service classes that are using HttpClient. To enable me to carry out unit testing I have a fake HttpMessageHandler that I would like to use for the HttpClient in my unit tests, whilst the HttpClient is instantiated as above however I am unable to apply the fake HttpMessageHandler as part of my unit tests.

What is the best way for the HttpClient in the service classes to remain a single instance throughout the application (one instance per endpoint), but to allow a different HttpMessageHandler to be applied during the unit tests?

One approach I have thought of would be not to use a static field to hold the HttpClient in the service classes, rather to allow it to be injected via constructor injection using a singleton lifecycle, which would allow me to specify a HttpClient with the desired HttpMessageHandler during unit tests, the other option I thought of would be to use a HttpClient Factory Class that instantiated the HttpClients in static fields that could then be retrieved by injecting the HttpClient factory into the service classes, again allowing a different implementation with the relevant HttpMessageHandler to be returned in unit tests. None of the above feel particularly clean however and it feels like there must be a better way?

Any questions, let me know.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
Chris Lawrence
  • 481
  • 1
  • 6
  • 14
  • if that's been used thru a controller, how about have the controller take an httpClient in the constructor and use Moq for unit testing? – Golois Mouelet Mar 30 '17 at 14:21
  • It is being used thru a controller, but rather than injecting HttpClient into the controller I inject a service, one implementation of which uses a HttpClient. There may be a need for additional implementations in the future that use mechanisms other than Http, which is why HttpClient is not supplied directly into the controller. – Chris Lawrence Mar 30 '17 at 14:28

5 Answers5

21

Adding to the conversation from the comments looks like you would need a HttpClient factory

public interface IHttpClientFactory {
    HttpClient Create(string endpoint);
}

and the implementation of the core functionality could look something like this.

public class DefaultHttpClientFactory : IHttpClientFactory, IDisposable
{
    private readonly ConcurrentDictionary<string, HttpClient> _httpClients;

    public DefaultHttpClientFactory()
    {
        this._httpClients = new ConcurrentDictionary<string, HttpClient>();
    }

    public HttpClient Create(string endpoint)
    {
        if (this._httpClients.TryGetValue(endpoint, out var client))
        {
            return client;
        }

        client = new HttpClient
        {
            BaseAddress = new Uri(endpoint),
        };

        this._httpClients.TryAdd(endpoint, client);

        return client;
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        foreach (var httpClient in this._httpClients)
        {
            httpClient.Value.Dispose();
        }
    }
}

That said, if you are not particularly happy with the above design. You could abstract away the HttpClient dependency behind a service so that the client does not become an implementation detail.

That consumers of the service need not know exactly how the data is retrieved.

live2
  • 3,771
  • 2
  • 37
  • 46
Nkosi
  • 235,767
  • 35
  • 427
  • 472
14

You think to complicated. All you need is a HttpClient factory or accessor with a HttpClient property and use it the same way the ASP.NET Core is allowing HttpContext to be injected

public interface IHttpClientAccessor 
{
    HttpClient Client { get; }
}

public class DefaultHttpClientAccessor : IHttpClientAccessor
{
    public HttpClient Client { get; }

    public DefaultHttpClientAccessor()
    {
        Client = new HttpClient();
    }
}

and inject this in your services

public class MyRestClient : IRestClient
{
    private readonly HttpClient client;

    public MyRestClient(IHttpClientAccessor httpClientAccessor)
    {
        client = httpClientAccessor.Client;
    }
}

registration in Startup.cs:

services.AddSingleton<IHttpClientAccessor, DefaultHttpClientAccessor>();

For unit-testing, just mock it

// Moq-esque

// Arrange
var httpClientAccessor = new Mock<IHttpClientAccessor>();
var httpHandler = new HttpMessageHandler(..) { ... };
var httpContext = new HttpContext(httpHandler);

httpClientAccessor.SetupGet(a => a.Client).Returns(httpContext);

// Act
var restClient = new MyRestClient(httpClientAccessor.Object);
var result = await restClient.GetSomethingAsync(...);

// Assert
...
Community
  • 1
  • 1
Tseng
  • 61,549
  • 15
  • 193
  • 205
  • 3
    I like the approach here, however this is going to give me a single HttpClient used to access all 3 endpoints. For a number of reasons I would like a separate HttpClient for each endpoint. So, Endpoint1 will be accessed by HttpClient instance1, Endpoint2 will be accessed by HttpClient instance2 for example. Perhaps the same approach could be used, with the HttpClients stored in a dictionary inside the HttpClientAccessor? With a method allowing retrieval by a string based key. Thoughts? – Chris Lawrence Mar 30 '17 at 14:25
  • 2
    Well, then you need a factory with a `Create(string endpoint)` method instead and for each endpoint you create a HttpContext and cache it, so you can return it on conclusive calls to Create method and use the endpoint as key for the dictionary – Tseng Mar 30 '17 at 14:51
  • @ChrisLawrence I second Tseng's last suggestion. That is how I would have approached it as well. – Nkosi Mar 30 '17 at 14:59
  • Should it be private readonly HttpClient client; or public readonly HttpClient client; – Naveed Ahmed Sep 20 '17 at 11:15
  • @NaveedAhmed: In the example I used a backing field, but you can also use the newer language features of C# such as assigning of read-only properties. That is for `DefaultHttpClientAccessor`. For the `RestClient` there is no reason to expose the `client` variable to the outside – Tseng Sep 20 '17 at 11:28
  • Thank you so much for your reply. On this line client = httpClientAccessor.HttpClient; I was receiving IHttpClientAccessor.HttpClient is inaccessible error due to protection level so I updated it to public HttpClient HttpClient { get; } in IHttpClientAccessor. Can you please update the code with the newer language features of C# that you are referring to. – Naveed Ahmed Sep 20 '17 at 11:48
  • 1
    @NaveedAhmed: read only properties are not the same as read only fields. **read-only property**: `HttpContext Client { get; }`; read-only field: `private readonly HttpContext client;`. Fields have no getter/setters – Tseng Sep 20 '17 at 11:54
  • Thank you @Tseng can you please also confirm if we now use _client.SendAsync(new HttpRequestMessage(HttpMethod.Post,"URL")) in different controllers actions, will it be using single instance of HttpClient and will it be safe under concurrent access, I mean for cases where different controller actions request different URL? – Naveed Ahmed Sep 21 '17 at 10:43
  • If you register it as singleton via `services.AddSingleton();` it will be a single instance. – Tseng Sep 21 '17 at 12:42
  • I think you could register a Lazy to reduce the number of new classes and interfaces instead of creating a lot of new interfaces. – Jonas Stensved Feb 01 '18 at 19:16
  • 1
    How does this differ from IHttpClientFactory? Was that not available at this time or is that a completely different idea? – Simon_Weaver Jan 27 '19 at 21:22
4

My current preference is to derive from HttpClient once per target endpoint domain and make it a singleton using dependency injection rather than use HttpClient directly.

Say I am making HTTP requests to example.com, I would have an ExampleHttpClient that inherits from HttpClient and has the same constuctor signature as HttpClient allowing you to pass and mock the HttpMessageHandler as normal.

public class ExampleHttpClient : HttpClient
{
   public ExampleHttpClient(HttpMessageHandler handler) : base(handler) 
   {
       BaseAddress = new Uri("http://example.com");

       // set default headers here: content type, authentication, etc   
   }
}

I then set ExampleHttpClient as singleton in my dependency injection registration and add a registration for HttpMessageHandler as transient as it will be created just once per http client type. Using this pattern I do not need to have multiple complicated registrations for HttpClient or smart factories to build them based on destination host name.

Anything that needs to talk to example.com should take a constructor dependency on ExampleHttpClient and then they all share the same instance and you get connection pooling as designed.

This way also gives you a nicer place to put stuff like default headers, content types, authorisation, base address etc, and helps prevent http config for one service leaking to another service.

alastairtree
  • 3,960
  • 32
  • 49
  • I like this idea. I generally only access 2-3 different endpoints so this could work quite well. I can’t help wondering what the solution here is if you had many many endpoints or more complex scenarios. – Simon_Weaver Jan 27 '19 at 21:26
1

I might be late to the party, but I've created a Helper nuget package that allows you to test HttpClient endpoints in unit tests.

NuGet: install-package WorldDomination.HttpClient.Helpers
Repo: https://github.com/PureKrome/HttpClient.Helpers

The basic idea is that you create the fake response payload and pass that the a FakeHttpMessageHandler instance to your code, which includes that fake response payload. Then, when your code tries to actually HIT that URI endpoint, it doesn't ... and just returns the fake response instead. MAGIC!

and here's a really simple example:

[Fact]
public async Task GivenSomeValidHttpRequests_GetSomeDataAsync_ReturnsAFoo()
{
    // Arrange.

    // Fake response.
    const string responseData = "{ \"Id\":69, \"Name\":\"Jane\" }";
    var messageResponse = FakeHttpMessageHandler.GetStringHttpResponseMessage(responseData);

    // Prepare our 'options' with all of the above fake stuff.
    var options = new HttpMessageOptions
    {
        RequestUri = MyService.GetFooEndPoint,
        HttpResponseMessage = messageResponse
    };

    // 3. Use the fake response if that url is attempted.
    var messageHandler = new FakeHttpMessageHandler(options);

    var myService = new MyService(messageHandler);

    // Act.
    // NOTE: network traffic will not leave your computer because you've faked the response, above.
    var result = await myService.GetSomeFooDataAsync();

    // Assert.
    result.Id.ShouldBe(69); // Returned from GetSomeFooDataAsync.
    result.Baa.ShouldBeNull();
    options.NumberOfTimesCalled.ShouldBe(1);
}
Pure.Krome
  • 84,693
  • 113
  • 396
  • 647
-3

HttpClient is used inside:

public class CustomAuthorizationAttribute : Attribute, IAuthorizationFilter
{
    private string Roles;
    private static readonly HttpClient _httpClient = new HttpClient();
Draken
  • 3,134
  • 13
  • 34
  • 54
Sumit
  • 1