HttpClient vs IHttpClientFactory in .NET

Introduction

In the realm of .NET Core development, managing HTTP requests efficiently is crucial for building robust and high-performance applications. Two commonly used approaches for making HTTP requests are the HttpClient class and the IHttpClientFactory interface. While both serve the purpose of sending HTTP requests, they differ in their usage, performance, and scalability. In this article, we delve into the differences between HttpClient and IHttpClientFactory, examining their strengths, weaknesses, and best practices.

HttpClient

HttpClient is a class that can be used for making HTTP requests and handling HTTP responses from web resources identified by a Uri in .NET. The HttpClient type was introduced in .NET Framework 4.5 (which was released in 2012) and is also available in .NET Core and .NET 5+.

Two ways that HttpClient was commonly used were:

  • A new HttpClient is created for every single request
  • The same HttpClient is used for all requests

The problem with creating a HttpClient for each request is that there is an overhead of instantiation, and beyond that, each HttpClient will hold open the socket that it used for some time after the request is completed. In case you have made a high volume of requests, you can face a socket exhaustion issue (it’s when a new HttpClient cannot acquire a socket to make a request). It’s true that when we use the HttpClient object inside of a code block, the HttpClient object is disposed of after it is used (because HttpClient implements IDisposable). However, the socket connection is not, and this can lead to a socket exhaustion problem when your traffic increases.

A better approach to use HttpClient, is to create your HttpClient object as singleton or static, instead of wrapping your HttpClient in a using, this way you will have a single instance of HttpClient for a given endpoint. But even with this approach, it’s better to configure the connection pooling behavior by configuring the PooledConnectionLifetime (this allows you to define how long a connection remains active when pooled, and once this lifetime expires, the connection will no longer be pooled or issued for future requests — It gets or sets how long a connection can be in the pool to be considered reusable), otherwise if the DNS TTL (time to live — is a setting that tells the DNS resolver how long to cache a query before requesting a new one) is expired and the domain name points to a new IP, your code will never know until it restarts, since there is no default logic in HttpClient to handle that.

In the code below, there is an example of how to do that:

public class HttpClientExample : IHttpClientExample
{
    private readonly IConfiguration _configuration;
    private readonly string _baseUri;

    private static HttpClient _httpClient = new HttpClient(new SocketsHttpHandler
    {
        PooledConnectionLifetime = TimeSpan.FromMinutes(1)
    });

    public HttpClientExample(IConfiguration configuration)
    {
        _configuration = configuration;
        _baseUri = _configuration.GetSection("CoinDeskApi:Url").Value;
    }

    public async Task<BtcContent?> GetBtcContent()
    {
        try
        {
            var response = await _httpClient.GetAsync($"{_baseUri}currentprice.json");

            var btcCurrentPrice = JsonSerializer.Deserialize<BtcContent>(await response.Content.ReadAsStringAsync());
            return btcCurrentPrice;
        }
        catch (Exception ex)
        {
            return await Task.FromException<BtcContent>(ex);
        }
    }
}

On line 6 there is a static property for the HttpClient, where it is configured the PooledConnectionLifeTime for 1 minute. On line 14 we get the API Url from the appsettings.json file. On line 21 the request is being made.

The API Url is configured in the appsettings.json file:

{
  "CoinDeskApi": {
    "Url": "https://api.coindesk.com/v1/bpi/"
  }
}

IHttpClientFactory

IHttpClientFactory was introduced in .NET Core 2.1 (also available in .NET 5+) and it provides a much-improved approach for getting an HTTP client. IHttpClientFactory can take advantage of resilient and transient-fault-handling third-party middleware with ease. It serves as a factory abstraction that can create HttpClient instances with custom configurations.

IHttpClientFactory solves both problems that were previously mentioned in the HttpClient topic, and this is done by pooling the HttpClientHandler (which does most of the work of HttpClient) and also disposing of HttpClientHandlers after a specified period. When a new HttpClientHandler is created for an endpoint, A DNS lookup is performed. You won’t wear out the sockets, and you will get a new IP address for each endpoint.

How to use it?

The first thing that must be done in order to use IHttpClientFactory, is to register it by calling AddHttpClient via Dependency Injection (it will be registered in the service collection as a singleton). In the Program.cs file, you can register like this:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient();

Once registered, now the class that will make use of IHttpClientFactory can receive it as a constructor parameter with DI. In the code below, there is an example of how to use it:

public class HttpClientFactoryExample : IHttpClientFactoryExample
{
    private readonly IConfiguration _configuration;
    private readonly IHttpClientFactory _httpClientFactory;

    private readonly string _baseUri;

    public HttpClientFactoryExample(IConfiguration configuration, IHttpClientFactory httpClientFactory)
    {
        _configuration = configuration;
        _baseUri = _configuration.GetSection("CoinDeskApi:Url").Value;
        _httpClientFactory = httpClientFactory;
    }

    public async Task<BtcContent?> GetBtcContent()
    {
        try
        {
            var client = _httpClientFactory.CreateClient();

            var apiUrl = $"{_baseUri}currentprice.json";
            var response = await client.GetAsync(apiUrl);

            var btcCurrentPrice = JsonSerializer.Deserialize<BtcContent>(await response.Content.ReadAsStringAsync());
            return btcCurrentPrice;
        }
        catch (Exception ex)
        {
            return await Task.FromException<BtcContent>(ex);
        }
    }
}

Note that on line 8, IHttpClientFactory is part of the constructor parameter, and we have a private variable to be used in this class. On line 11, we get the API Url from the appsettings.json file. On line 19 there is a call to CreateClient, this is a IHttpClientFactory method which returns the HttpClient Object. On line 22, the request is executed.

Named Clients

It’s also possible to have named clients, you can do that by registering it via DI (you can register how many you want). This is how you can configure it in a Program.cs file:

builder.Services.AddHttpClient("coinDeskApi", client =>
{
    client.BaseAddress = new Uri(builder.Configuration.GetValue<string>("CoinDeskApi:Url"));
});

This is how you can use the named client in your class:

As in the previous example, IHttpClientFactory is part of the constructor parameter, and we have a private variable to be used in this class (line 3). On line 14, there is a call to CreateClient, but now with the name that was configured via DI. On line 16 the request is executed.

There are also other ways to work with IHttpClientFactory, if you want to know more about it, check the Consumption patterns at Microsoft Docs.

IHttpClientFactory Benefits

  • Exposes HttpClient class as DI ready type.
  • Provides a central location for naming and configuring logical HttpClient instances.
  • Integrates well with Polly (a .NET resilience and transient-fault-handling library).
  • Avoid common DNS problems by managing HttpClientHandler lifetimes.
  • Adds configurable logging (via ILogger) for all the requests sent through clients created by the factory.

For most cases, the best approach for making requests to external API is by using IHttpClientFactory. Microsoft’s recommendation is to use IHttpClientFactory or use a static or singleton HttpClient with PooledConnectionLifetime configured with the desired interval.

Best Practices for Using IHttpClientFactory

  • Use Named Clients: Define named HttpClient instances for specific API endpoints or services to encapsulate configuration settings and promote code readability.
  • Configure Policies: Implement resilient HTTP request policies such as retry, circuit breaker, and timeout policies to handle transient faults and improve application robustness.
  • Dispose Responsibly: While IHttpClientFactory manages the lifecycle of HttpClient instances, it's essential to dispose of resources explicitly when necessary, especially in long-lived applications.

Conclusion

In the landscape of .NET Core web development, choosing between HttpClient and IHttpClientFactory depends on various factors such as performance requirements, scalability, and resource management concerns. While HttpClient offers simplicity and flexibility, IHttpClientFactory provides a more efficient and scalable approach to HTTP request management. By understanding their differences and leveraging best practices, developers can build resilient and high-performance applications that meet the demands of modern web development.


Similar Articles