Skip to Content
Course content

209: HttpClientFactory Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've spent any time in C#, you've been conditioned to follow a very specific rule: if a class implements IDisposable, wrap it in a using block. It's the gold standard for preventing memory leaks. But when it comes to HttpClient, that instinct will actually break your application in production.

The "Dispose Everything" Instinct

I see this constantly in code reviews. A developer wants to fetch some data from an external API, so they write something like this:

public async Task<string> GetLatestPriceAsync(string symbol)
{
    using (var client = new HttpClient())
    {
        return await client.GetStringAsync($"https://api.exchangerate.host/latest?base={symbol}");
    }
}

On the surface, this looks perfect. It's clean, it's safe, and it disposes of the client as soon as the method finishes. But here is the catch: HttpClient is a bit of a liar. When you dispose of the client, the underlying socket isn't actually closed immediately. It lingers in a state called TIME_WAIT.

If your app is low-traffic, you'll never notice. But if you're building a service that hits an API hundreds of times a minute, you'll run out of available sockets. Your app will start throwing SocketException errors, and you'll be left scratching your head wondering why a "disposed" object is crashing your server. This is what we call socket exhaustion.

Letting the Factory Manage the Handlers

You might think the solution is to just make the HttpClient a static singleton. That fixes the socket issue, but it introduces a new one: DNS changes. If the API you're calling updates its IP address, a singleton client will keep trying to hit the old IP until you restart the entire process. Not ideal for a high-availability system.

This is why we use the IHttpClientFactory pattern. Instead of you managing the lifetime of the client, the factory manages the lifetime of the HttpMessageHandler. It pools these handlers and rotates them out periodically, ensuring you don't exhaust your sockets while still respecting DNS updates. I personally find this to be one of those "invisible" wins in .NET—once it's configured, you just stop worrying about network plumbing.

Typed Clients: Organizing Your API Logic

While you can ask the factory for a generic client, the "pro move" is using a Typed Client. This allows you to encapsulate all the logic for a specific API into its own class, making your code much easier to test and maintain. Let's look at how we'd implement a PriceTrackerService using this pattern.

// 1. Define the typed client
public class PriceTrackerService
{
    private readonly HttpClient _httpClient;

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

    public async Task<decimal> GetPriceAsync(string symbol)
    {
        // The client is already configured via the factory
        var response = await _httpClient.GetFromJsonAsync<PriceResponse>($"price/{symbol}");
        return response?.Price ?? 0;
    }
}

public record PriceResponse(decimal Price);

Now, the magic happens in your Program.cs (or Startup.cs). You tell the dependency injection container how to build this client:

var builder = WebApplication.CreateBuilder(args);

// This registers the PriceTrackerService and tells the factory
// to provide it with a pre-configured HttpClient.
builder.Services.AddHttpClient<PriceTrackerService>(client =>
{
    client.BaseAddress = new Uri("https://api.prices.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.Timeout = TimeSpan.FromSeconds(5);
});

var app = builder.Build();

Now, whenever you inject PriceTrackerService into a controller or another service, .NET handles the instantiation and the handler pooling behind the scenes. You get the best of both worlds: the convenience of a short-lived object and the efficiency of a long-lived connection.




📋 Practical Task

Refactoring the WeatherMonitor for Socket Efficiency

You have inherited a legacy WeatherMonitor class that is causing intermittent SocketException crashes during peak usage. The current implementation creates a new HttpClient inside a using block for every single request.

Your Task:

  1. Remove the using` block from the GetWeatherAsync method.
  2. Convert the WeatherMonitor into a Typed Client by accepting an HttpClient through its constructor.
  3. Update the Program.cs configuration to register the WeatherMonitor using builder.Services.AddHttpClient<WeatherMonitor>().
  4. Set the BaseAddress to https://api.weather.com/v1/ inside the AddHttpClient configuration lambda.

Ensure that the WeatherMonitor no longer instantiates the client itself, and that the dependency injection system is responsible for providing the managed client.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.