Your HttpClient is Broken: The IHttpClientFactory Playbook

🌐 Making HTTP Requests in .NET: Best Practices

When developing API-driven services in .NET, the strategy used for managing HTTP communication is critical to application performance, reliability, and scalability. This document details the modern, recommended approach using IHttpClientFactory.

HttpClient vs. IHttpClientFactory in Modern Architectures

Historically, developers often instantiated HttpClient directly:

C#

using var client = new HttpClient();

// Use client to make a request

While simple, this pattern is highly problematic in production environments, especially in microservice and high-load architectures.

⚠️ Common Pitfalls of Direct HttpClient Instantiation

Pattern

Issue

Description

new HttpClient() in a using block

Socket Exhaustion

Each new instance allocates a new HttpMessageHandler and potentially a new socket connection. While the HttpClient instance is disposed, the underlying socket may take time to close, leading to the application running out of available sockets under heavy load.

Static or Singleton HttpClient

Stale DNS Entries

A long-lived static or singleton HttpClient instance reuses the underlying handler indefinitely. The handler does not respect Time-To-Live (TTL) changes for DNS records, meaning it will never refresh the DNS entry for a target service, leading to failed requests if the service's IP address changes.

 

The Modern Solution: IHttpClientFactory

.NET introduced IHttpClientFactory to address these limitations. It acts as a managed factory that provides a robust, modern approach to HttpClient management.

Key Benefits of IHttpClientFactory:

  • Handler Pooling: The factory manages the lifecycle of the underlying HttpMessageHandler objects, ensuring that they are pooled and reused efficiently, which prevents socket exhaustion.
  • DNS Refresh: Handlers in the pool have a configurable lifetime, after which they are refreshed, mitigating the stale DNS entry problem.
  • Resilience Integration: It natively integrates with libraries like Polly to easily apply resilience patterns (e.g., Retry, Timeout, Circuit Breaker) across all outgoing HTTP calls.
  • Testability: It integrates with .NET's dependency injection (DI) system, making the code more testable.

🛠️ IHttpClientFactory Usage Patterns

The IHttpClientFactory is registered via the Microsoft.Extensions.Http NuGet package and is the officially recommended approach.

1. Basic Usage (Named or Typed Clients Recommended)

This pattern injects IHttpClientFactory directly into a service and uses it to create a transient HttpClient instance for each request.

Registration (Implicit): The factory is typically registered when configuring the DI container.

Usage Example:

C#

public class ExternalApiService
{
    private readonly IHttpClientFactory _factory; 

    // Factory is injected via DI
    public ExternalApiService(IHttpClientFactory factory)
    {
        _factory = factory;
    } 

    public async Task<string> GetUserData(int userId)
    {
        // Creates a new HttpClient instance, but uses a pooled, managed handler
        var client = _factory.CreateClient();
        var response = await client.GetAsync($"https://api.external.com/users/{userId}");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }

2. Named Clients

Named clients are useful for configuring multiple external services, each with distinct settings (Base Address, Headers, Timeouts, etc.) and handlers.

Registration in Program.cs:

C#

builder.Services.AddHttpClient("GraphAPI", client =>
{
    client.BaseAddress = new Uri("https://graph.microsoft.com");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.Timeout = TimeSpan.FromSeconds(15);
});

Usage:

C#

// Inject IHttpClientFactory
public class GraphConsumerService
{
    private readonly IHttpClientFactory _factory;
    // ... constructor ... 
    public async Task<string> GetUserData()
    {
        // Retrieve the specifically configured client
        var client = _factory.CreateClient("GraphAPI");
        var response = await client.GetAsync("/v1.0/me");
        // ...
        return await response.Content.ReadAsStringAsync();
    }
}

3. Typed Clients ( Best Practice)

Typed Clients are the most recommended approach for production-grade systems. This pattern registers a custom class (the "Typed Client") that takes an HttpClient in its constructor.

Benefits:

  • Decoupled: The client logic is encapsulated in a dedicated service.
  • Testable: The HttpClient is injected, making it easy to mock for unit testing.
  • IntelliSense: Clear method signatures and type safety.

Typed Client Implementation:

C#

public class GraphAPIService
{
    // The HttpClient is provided by the IHttpClientFactory-managed instance
    private readonly HttpClient _httpClient; 

    public GraphAPIService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    } 

    public async Task<string> GetUserDetails(string id)
    {
        // Note: BaseAddress is configured at registration, allowing a relative URI here
        var response = await _httpClient.GetAsync($"/{id}");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
}

Registration in Program.cs:

C#

// Registers GraphAPIService and configures the underlying HttpClient
builder.Services.AddHttpClient<GraphAPIService>(client =>
{
    client.BaseAddress = new Uri("https://graph.microsoft.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
});


💡 Conclusion

Modern, distributed, and cloud-native applications require resilient and efficiently managed HTTP communication. The use of new HttpClient() directly is considered an anti-pattern. IHttpClientFactory is the official and recommended standard for building stable, scalable, testable, and high-performance .NET applications, with Typed Clients being the preferred usage pattern.

 

Comments

Popular posts from this blog

Data Protection API (DPAPI) system in ASP.NET Core

Get Started with GIT Repository and VSTS