Typed HttpClient vs IHttpClientFactory

14.8k views Asked by At

Is there any difference between the following 2 scenarios of setting up HttpClient?

Should I prefer one to another?

Typed client:

public class CatalogService 
{
    private readonly HttpClient _httpClient;
    
    public CatalogService(HttpClient httpClient) {
        _httpClient = httpClient;
    }
    
    public async Task<string> Get() {
        var response = await _httpClient.GetAsync();
        ....
    }
    
    public async Task Post() {
        var response = await _httpClient.PostAsync();
        ...
    }
}
// Startup.cs
//Add http client services at ConfigureServices(IServiceCollection services)
services.AddHttpClient<ICatalogService, CatalogService>();

IHttpClientFactory:

public class CatalogService 
{
    private readonly IHttpClientFactory _factory;
    
    public CatalogService(IHttpClientFactory factory) {
        _factory = factory;
    }
    
    public async Task<string> Get() {
        var response = await _factory.CreateClient().GetAsync();
        ....
    }
    
    public async Task Post() {
        var response = await _factory.CreateClient().PostAsync();
        ...
    }
}
// Startup.cs
//Add http client services at ConfigureServices(IServiceCollection services)
services.AddHttpClient();
```
3

There are 3 answers

0
Han Zhao On

IMO, I will go with passing HttpClient. The reasons are,

  1. KISS Principle - What CatalogService really needs is a HttpClient. The service does not care about how to get a client.
  2. Single Responsibility Principle (SRP) - Say tomorrow you have to keep two instances of CatalogService to send requests to two different endpoints,
    • You can pass in a IHttpClientFactory and implement routing inside CatalogService, but that breaks SRP.
    • Or, you can create a CatalogServiceFactory. That factory gets IHttpClientFactory passed in and implement routing inside. That is also known as Separation of Concerns.
0
Peter Csala On

Versioning:

  • UPDATED at 07 Oct 22 by adding named and typed client

I think the biggest difference reveals itself when you look at them from the consumption point of view.

Typed client

You will receive a HttpClient instance, which might have been decorated with some resilient strategy against transient failure and with some default values. You might even receive a client where the BaseUrl is already set.

So, this approach can be particularly useful if you need to hide a REST API client behind a strongly-typed service layer.

Named client

This technique can shine when you need several instances from a specific client or when you need several different clients. If you have registered several different clients with different names then you can retrieve them easily via a single API.

So, this approach could be useful if you need to call different downstream systems and you need to aggregate their results.

Named and typed client

There is a third option which is a combination of the above two. You get the strongly-typed API of the typed clients and the unique naming capability of named clients.

This can be particularly useful when you want to use the same typed client API against different domains (like there is a primary and a secondary site). Or you want to have slightly different Polly policies (different downstream systems might need different timeout settings) ...

Here I have detailed how can you create and use the different clients.

Good to read articles

2
tsvedas On

Having abstraction (i.e. IHttpClient) is far better and more praised by the community. It allows you to assign HttpClient as well as your custom-written IHttpClient without ever changing CatalogService.

This is very critical point when creating large systems as your dependencies on concrete implementations decreases, so does the maintenance costs.

Furthermore, using abstractions allows showing what usage was actually intended and reduces the possible distractions. One example:

public interface MyInterface
{
    void UsefulMethod();
}

public class MyClass : MyInterface
{
    public float variable1;
    public float moreBurden;
    public float thisIsNotRequiredAtAll;

    public void UsefulMethod() {}
    public void AnotherMethod() {}
    public void MoreNoiseMethod() {}
}

public class MyService
{
    private MyClass _myClass;

    public MyService(MyClass myClass)
    {
        _myClass = myClass;
    }

    public void MyOnlyMethod()
    {
        _myClass.UsefulMethod();
    }
}

In this case, you are only using single method, but your service can access tons of unnecessary information, thus distracting from the goal. And to create MyService, its creator must be able to create instance of MyClass.

Now image MyService is written like

public class MyService
{
    private IMyInterface _myInterface;

    public MyService(IMyInterface myInterface)
    {
        _myInterface = myInterface;
    }
   
    public void MyOnlyMethod()
    {
        _myInterface.UsefulMethod();
    }
}

Now, the purpose of _myInterface is clear - you only need a specific subset of methods. MyService only has access to that single method and isn't distracted by all the possible implementation details.