Table of contents

How do I reuse HttpClient (C#) instances effectively?

Reusing HttpClient instances effectively is crucial for performance and preventing socket exhaustion in C# applications. Creating a new HttpClient for each request can lead to SocketException errors under heavy load due to port exhaustion.

Why HttpClient Reuse Matters

Each HttpClient instance manages its own connection pool. Creating multiple instances unnecessarily: - Consumes system resources - Prevents connection pooling benefits - Can exhaust available TCP ports - Increases DNS lookup overhead

Recommended Approaches

1. IHttpClientFactory (Preferred for ASP.NET Core)

IHttpClientFactory is the modern, recommended approach for managing HttpClient instances in ASP.NET Core applications. It handles connection pooling, DNS changes, and lifecycle management automatically.

// Startup.cs or Program.cs
public void ConfigureServices(IServiceCollection services)
{
    // Basic registration
    services.AddHttpClient();

    // Named client with configuration
    services.AddHttpClient("ApiClient", client =>
    {
        client.BaseAddress = new Uri("https://api.example.com/");
        client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
        client.Timeout = TimeSpan.FromSeconds(30);
    });

    // Typed client registration
    services.AddHttpClient<WeatherService>();
}

// Using named client
public class ApiService
{
    private readonly HttpClient _httpClient;

    public ApiService(IHttpClientFactory httpClientFactory)
    {
        _httpClient = httpClientFactory.CreateClient("ApiClient");
    }

    public async Task<string> GetDataAsync()
    {
        var response = await _httpClient.GetAsync("data");
        return await response.Content.ReadAsStringAsync();
    }
}

// Using typed client
public class WeatherService
{
    private readonly HttpClient _httpClient;

    public WeatherService(HttpClient httpClient)
    {
        _httpClient = httpClient;
        _httpClient.BaseAddress = new Uri("https://api.weather.com/");
    }

    public async Task<string> GetWeatherAsync(string city)
    {
        var response = await _httpClient.GetAsync($"weather/{city}");
        return await response.Content.ReadAsStringAsync();
    }
}

2. Static HttpClient (For Simple Scenarios)

For simple console applications or when not using dependency injection:

public static class HttpClientProvider
{
    private static readonly HttpClient _httpClient = new HttpClient()
    {
        Timeout = TimeSpan.FromSeconds(30)
    };

    static HttpClientProvider()
    {
        _httpClient.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
    }

    public static HttpClient Instance => _httpClient;
}

// Usage
public class DataService
{
    public async Task<string> FetchDataAsync(string url)
    {
        var client = HttpClientProvider.Instance;
        var response = await client.GetAsync(url);
        return await response.Content.ReadAsStringAsync();
    }
}

3. Singleton Pattern with Lazy Initialization

For thread-safe lazy initialization:

public sealed class HttpClientSingleton
{
    private static readonly Lazy<HttpClient> _lazyHttpClient = 
        new Lazy<HttpClient>(() => CreateHttpClient());

    public static HttpClient Instance => _lazyHttpClient.Value;

    private static HttpClient CreateHttpClient()
    {
        var client = new HttpClient();
        client.BaseAddress = new Uri("https://api.example.com/");
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
        client.Timeout = TimeSpan.FromSeconds(30);
        return client;
    }
}

// Usage
var response = await HttpClientSingleton.Instance.GetAsync("endpoint");

Advanced Configuration Examples

Multiple Clients with Different Configurations

services.AddHttpClient("AuthorizedClient", client =>
{
    client.BaseAddress = new Uri("https://secure-api.example.com/");
    client.DefaultRequestHeaders.Authorization = 
        new AuthenticationHeaderValue("Bearer", "your-token");
})
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
{
    UseCookies = false,
    UseProxy = false
});

services.AddHttpClient("PublicClient", client =>
{
    client.BaseAddress = new Uri("https://public-api.example.com/");
    client.Timeout = TimeSpan.FromSeconds(10);
});

With Polly for Resilience

services.AddHttpClient("ResilientClient")
    .AddPolicyHandler(GetRetryPolicy())
    .AddPolicyHandler(GetCircuitBreakerPolicy());

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .WaitAndRetryAsync(
            retryCount: 3,
            sleepDurationProvider: retryAttempt => 
                TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}

static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(
            handledEventsAllowedBeforeBreaking: 3,
            durationOfBreak: TimeSpan.FromSeconds(30));
}

What NOT to Do

// ❌ DON'T: Create new HttpClient for each request
public async Task<string> BadExample(string url)
{
    using var client = new HttpClient(); // Creates new connection pool each time
    var response = await client.GetAsync(url);
    return await response.Content.ReadAsStringAsync();
}

// ❌ DON'T: Dispose static HttpClient
public static class BadSingleton
{
    public static HttpClient Client = new HttpClient();

    public static void Cleanup()
    {
        Client.Dispose(); // Don't dispose static instances
    }
}

Best Practices Summary

  1. Use IHttpClientFactory in ASP.NET Core applications
  2. Create long-lived instances for simple scenarios
  3. Configure timeouts to prevent hanging requests
  4. Set appropriate headers once during initialization
  5. Don't dispose static or singleton instances
  6. Use typed clients for better organization and testing
  7. Consider resilience patterns like retry and circuit breaker
  8. Monitor connection pool usage in production

By following these patterns, you'll achieve optimal performance while avoiding common pitfalls like socket exhaustion and DNS resolution issues.

Try WebScraping.AI for Your Web Scraping Needs

Looking for a powerful web scraping solution? WebScraping.AI provides an LLM-powered API that combines Chromium JavaScript rendering with rotating proxies for reliable data extraction.

Key Features:

  • AI-powered extraction: Ask questions about web pages or extract structured data fields
  • JavaScript rendering: Full Chromium browser support for dynamic content
  • Rotating proxies: Datacenter and residential proxies from multiple countries
  • Easy integration: Simple REST API with SDKs for Python, Ruby, PHP, and more
  • Reliable & scalable: Built for developers who need consistent results

Getting Started:

Get page content with AI analysis:

curl "https://api.webscraping.ai/ai/question?url=https://example.com&question=What is the main topic?&api_key=YOUR_API_KEY"

Extract structured data:

curl "https://api.webscraping.ai/ai/fields?url=https://example.com&fields[title]=Page title&fields[price]=Product price&api_key=YOUR_API_KEY"

Try in request builder

Related Questions

Get Started Now

WebScraping.AI provides rotating proxies, Chromium rendering and built-in HTML parser for web scraping
Icon