Card image cap

Exploring ASP.NET Core Fundamentals - Using IHttpClientFactory for API Efficiency

ASP.NET Core  • Posted 2 months ago

So far we have looked at how HttpClient works and why "not" disposing HttpClient instance for everytime is a lot important followed by an alternative approach for HttpClient instantiation, which is a popular alternative. While all that is important to be aware of, AspNetCore takes a different approach in using HttpClient instances and provides us with a better alternative - the DI way. In this article, we'll discuss more about how we can efficiently reuse HttpClient instances by means of dependency injection and how IHttpClientFactory helps us with a powerful approach.

The dotnet core approach:

The dotnet core approach for HttpClient brings in a Factory implementation for the instantiation, using dependency injection. Instead of creating a static constructor and maintaining a static instance of HttpClient at all times for reuse, dotnetcore recommends using IHttpClientFactory injectable for creating client. The IHttpClientFactory internally takes care of the socket connections and results in much improved code efficiency. The IHttpClientFactory provides us with HttpClient instance and makes sure that the instances are not exhausting the socket connection pool, which results in SocketException errors.

We can register IHttpClientFactory in three ways:

  1. Simple Client
  2. Named Client
  3. Typed Client
// simple usage
services.AddHttpClient();

// named client
services.AddHttpClient("myapiclient", (client) => {
  client.BaseAddress("http://myapi.com/");
});

// typed client
services.AddHttpClient<IMyHttpClientHandler, MyHttpClientHandler>();

Basic Client:

In the basic approach, we register IHttpClientFactory as a service and inject the instance where ever required through the constructor. On the received IHttpClientFactory instance, we can call CreateClient() method which returns a HttpClient instance for our use. The IHttpClientFactory takes care of the instance management and socket management and so we don't need to think about disposing the instance.

Example:

public class TestController 
{

	private readonly HttpClient client;

	public TestController(IHttpClientFactory clientFactory) 
    {
		client = clientFactory.CreateClient();
	}

	private async Task SomeLogic() 
    {

		var httpRequestMessage = new HttpRequestMessage();
		httpRequestMessage.Uri = "http://myapi.com/api/getstatus";
		httpRequestMessage.Method = HttpMethod.Get;

		var response = await client.SendAsync(httpRequestMessage);

		var responseContent = await response.Content.ReadAsStringAsync();

        ...
	}
}

As explained above, the IHttpClientFactory is injected into the constructor and a new client is created using the CreateClient method on the instance, which returns a HttpClient. You may observe that the instance management is taken care by the framework and a pool of connections are only reused by the client factory.

Named Client:

The Named client approach works well in scenarios where API calls might be made onto multiple API domains, which are fixed beforehand. In such cases, instead of manually creating clients for different sources we can create groups of instances based on sources and respective client instance be injected via those registered "names".

Example:

public class TestController 
{

	private readonly HttpClient client;

	public TestController(IHttpClientFactory clientFactory) 
    {
        // a client group is created with name "myapiclient"
        // which points to an already defined API domain
        // or a base address
		client = clientFactory.CreateClient("myapiclient");
	}

	private async Task SomeLogic() 
    {
		// since the BaseAddress is already assigned in the client
        // we won't assign it a second time
		var httpRequestMessage = new HttpRequestMessage();
		httpRequestMessage.Uri = "api/getstatus";
		httpRequestMessage.Method = HttpMethod.Get;
	
		var response = await client.SendAsync(httpRequestMessage);

		var responseContent = await response.Content.ReadAsStringAsync();

        ...
	}

}

As explained above, a client instance of the type defined by the name "myapiclient" is created which comes with all the settings (the base address or the headers) predefined (in the startup class) and so groups of client objects with varied settings and configurations can be created and injected efficiently.

Typed Approach:

The Typed Client approach is the most structured approach, where the HttpClient instance is directly injected into a user defined class and then that type is injected wherever required. In this approach, we wrap the HttpClient functionality with our own implementation which is then reused everywhere. This gives us one advantage of encapsulating any additional logic to be included before an API call (such as a Fetch Token for the API call or cache checking and so on) and let the other components just use the completed wrapper instead of the client itself.

Example:

//service definition in startup.cs file
services.AddHttpClient<
    IMyHttpClientHandler, MyHttpClientHandler>();
public class IMyHttpClientHandler 
{
	Task<string> GetStatus();
}

public class MyHttpClientHandler 
    : IMyHttpClientHandler {

	private readonly HttpClient client;
    private readonly ITokenManager token;

	public MyHttpClientHandler(
        HttpClient httpClient, 
        ITokenManager token) 
    {
		client = httpClient;
        token = token;
	}

	public async Task<string> GetStatus() 
    {
	
		var httpRequestMessage = new HttpRequestMessage();
		httpRequestMessage.Uri = "api/getstatus";
		httpRequestMessage.Method = HttpMethod.Get;
        httpRequestMessage.Headers
            .TryAddWithoutValidation("Authorization", token.GetToken())
	
		var response = await client.SendAsync(httpRequestMessage);

		var responseContent = await response.Content.ReadAsStringAsync();
		return resonseContent;
	}
}

//usage
public class TestController 
{
	private readonly IMyHttpClientHandler service;

	public TestController(IMyHttpClientHandler service)
    {
		this.service = service;
	}

	private async Task SomeLogic() 
    {
		var response = await service.GetStatus();
	}
}

In the above example, since we have defined to inject HttpClient into a type IMyHttpClientHandler and is implemented by MyHttpClientHandler, the HttpClient is injected into the constructor of MyHttpClientHandler, and we can inject IMyHttpClientHanlder instance into our calling client. This provides a proper decoupling of logic and maintainability.

One can ask what would be the nature of the service created for IMyHttpClientHandler in the above approach. Since dotnet core provides three types of services based on their object lifetimes, which category does this one might fall into? Well, its transient (as explained vaguely in the documentation) I suppose.

We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept