How to use HttpClient and IHttpClientFactory in .NET Core

By FoxLearn 1/9/2025 2:13:05 AM   10
When working with APIs in C#, the HttpClient class is essential for making HTTP requests such as GET, PUT, POST, or DELETE.

However, one common pitfall is incorrectly instantiating HttpClient for every request. This can result in socket exhaustion due to improper handling of connections.

Here's an example of the wrong way to use HttpClient:

public static async Task<string> GetData(string url)
{
    using (var httpClient = new HttpClient())
    {
        using (var response = await httpClient.GetAsync(url))
        {
            string data = await response.Content.ReadAsStringAsync();
            return data;
        }
    }
}

Each time GetData is called, a new HttpClient instance is created and disposed of, which can quickly exhaust available sockets, especially in high-throughput applications.

To address this issue, we use an IHttpClientFactory, which manages the lifecycle of HttpClient instances for you. This ensures that HTTP connections are reused efficiently.

Register IHttpClientFactory in Startup

First, in your Program.cs or Startup.cs (depending on your project structure), you need to register IHttpClientFactory:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient("ApiHttpClient");
var app = builder.Build();
await app.RunAsync();

This creates a named HttpClient instance available throughout the application. You can register multiple named clients if needed, but for now, "ApiHttpClient" will be sufficient.

Inject IHttpClientFactory Into Your Classes

Now, instead of directly instantiating HttpClient, you should inject IHttpClientFactory into your classes.

Here's how your repository or service class might look:

namespace MyApp.Services
{
    public class ApiService
    {
        private readonly IHttpClientFactory _httpClientFactory;

        public ApiService(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }

        public async Task<string> FetchDataAsync(string endpoint)
        {
            var client = _httpClientFactory.CreateClient("ApiHttpClient");

            var response = await client.GetAsync(endpoint);
            if (!response.IsSuccessStatusCode)
            {
                throw new Exception($"Error: {response.StatusCode} - {response.ReasonPhrase}");
            }

            return await response.Content.ReadAsStringAsync();
        }
    }
}

By calling _httpClientFactory.CreateClient("ApiHttpClient"), we ensure that the HttpClient instance is reused across requests. This avoids socket exhaustion and enhances performance by maintaining persistent connections.