Pitfalls and Best Practices of HttpClient in .NET Development

In .NET project development, HttpClient is almost an essential tool for calling external APIs. It is easy to use, but if you do not understand its internal mechanisms, it can lead to pitfalls and even serious issues such as service avalanche and port exhaustion.

Today, we will review common pitfalls in HttpClient and their corresponding best practices.

1. <span>using</span> leading to port exhaustion

Many developers are accustomed to writing:

using (var client = new HttpClient())
{
    var response = await client.GetAsync(url);
}

On the surface, this is a standard C# resource release pattern, but HttpClient maintains a connection pool. Frequently creating and releasing it can lead to port exhaustion (Socket Exhaustion), ultimately causing request failures.

Best Practice: HttpClient should be reused. In ASP.NET Core, it is recommended to use <span>IHttpClientFactory</span> to manage the lifecycle uniformly:

builder.Services.AddHttpClient("MyClient", client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
});

2. Global Header Pollution

If you set the Header like this:

httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer xxx");

Note! All subsequent requests will carry this Header. In multi-user and multi-service scenarios, this can lead to serious issues.

Best Practice: It is recommended to set the Header at the request level.

var request = new HttpRequestMessage(HttpMethod.Get, "/data");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);

3. Forgetting to Release <span>HttpResponseMessage</span>

Many times we write:

var response = await httpClient.GetAsync("/data");
var content = await response.Content.ReadAsStringAsync();

If you do not manually <span>Dispose</span>, the connection will be occupied for a long time.

Best Practice: Use <span>using</span> syntax to ensure release.

using var response = await httpClient.GetAsync("/data");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();

4. Timeout Not Set

The default timeout for HttpClient is 100 seconds. In high-concurrency environments, if the target service responds slowly, it may lead to request accumulation.

Best Practice: Set a reasonable timeout.

httpClient.Timeout = TimeSpan.FromSeconds(10);

Or use <span>CancellationToken</span> for precise control:

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await httpClient.GetAsync("/data", cts.Token);

5. DNS Cache Issues

In early .NET Framework versions, a singleton HttpClient would cause the DNS cache to never expire. After a domain name change, the service would still request the old IP.

This has been optimized in .NET Core, but for older projects, you can manually specify the connection lifecycle:

builder.Services.AddHttpClient("MyClient")
    .ConfigurePrimaryHttpMessageHandler(() =>
        new SocketsHttpHandler
        {
            PooledConnectionLifetime = TimeSpan.FromMinutes(2)
        });

6. Large File Requests Causing Memory Explosion

Many people are accustomed to using <span>ReadAsStringAsync()</span>, but if the return is a large file (such as logs or videos), it can directly blow up the memory.

Best Practice: Use streaming.

using var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
await using var stream = await response.Content.ReadAsStreamAsync();
// Manually process the stream

7. Missing Retry Mechanism

Network calls are bound to fail; without a retry mechanism, service stability will be greatly compromised.

Best Practice: Combine with Polly to add a retry strategy.

builder.Services.AddHttpClient("MyClient")
    .AddPolicyHandler(Policy
        .Handle<HttpRequestException>()
        .OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
        .WaitAndRetryAsync(3, retry => TimeSpan.FromSeconds(retry)));

·············· END ··············

If you find this article helpful, feel free to like, bookmark, and share it with more developers! Let’s learn and progress together!

Leave a Comment