Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
codewithmukesh avatar

Httpclient Factory

  • 133 installs
  • 629 repo stars
  • Updated August 3, 2026
  • codewithmukesh/dotnet-claude-kit

Configures IHttpClientFactory typed and keyed clients with DelegatingHandlers and standard resilience for safe outbound HTTP calls in .NET.

About

Configures IHttpClientFactory and typed/keyed HTTP clients in .NET 10 with DelegatingHandlers and Microsoft.Extensions.Http.Resilience for retry, circuit breaker, and timeout. A developer uses it to make outbound HTTP calls safely without socket exhaustion.

  • Uses IHttpClientFactory with named/typed/keyed clients to avoid socket exhaustion
  • Adds AddStandardResilienceHandler and DelegatingHandlers for auth, correlation, and logging

Httpclient Factory by the numbers

  • 133 all-time installs (skills.sh)
  • Ranked #2,706 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill httpclient-factory

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs133
repo stars629
Last updatedAugust 3, 2026
Repositorycodewithmukesh/dotnet-claude-kit

What it does

Configures IHttpClientFactory typed and keyed clients with DelegatingHandlers and standard resilience for safe outbound HTTP calls in .NET.

Files

SKILL.mdMarkdownGitHub ↗

HttpClient Factory

Core Principles

1. Never `new HttpClient()` per request — Raw HttpClient creation causes socket exhaustion under load and ignores DNS changes. Use IHttpClientFactory to manage handler lifetimes. 2. Keyed clients over typed clients — Keyed DI (.AddAsKeyed()) is the recommended pattern in .NET 10. Typed clients captured in singletons silently break handler rotation. 3. Resilience is not optional — Every external HTTP call needs retry, circuit breaker, and timeout. AddStandardResilienceHandler() provides sensible defaults in one line. 4. DelegatingHandlers for cross-cutting concerns — Auth tokens, correlation IDs, and logging belong in the handler pipeline, not scattered across service methods.

Patterns

Named Client with Resilience

builder.Services.AddHttpClient("github", client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.DefaultRequestHeaders.UserAgent.ParseAdd("MyApp/1.0");
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));
})
.AddStandardResilienceHandler();

// Usage via factory
public sealed class GitHubService(IHttpClientFactory factory)
{
    public async Task<Repo?> GetRepoAsync(string owner, string name, CancellationToken ct)
    {
        var client = factory.CreateClient("github");
        return await client.GetFromJsonAsync<Repo>($"repos/{owner}/{name}", ct);
    }
}

Keyed Client (Recommended in .NET 10)

Combines named client configurability with direct injection. No string lookups.

builder.Services.AddHttpClient("payments", client =>
{
    client.BaseAddress = new Uri("https://api.payments.example.com/");
})
.AddStandardResilienceHandler()
.AddAsKeyed();  // Register as keyed scoped service

// Inject directly — no IHttpClientFactory needed
app.MapPost("/charge", async (
    [FromKeyedServices("payments")] HttpClient httpClient,
    ChargeRequest request,
    CancellationToken ct) =>
{
    var response = await httpClient.PostAsJsonAsync("charges", request, ct);
    return response.IsSuccessStatusCode
        ? TypedResults.Ok()
        : TypedResults.Problem("Payment failed");
});

Global opt-in: builder.Services.ConfigureHttpClientDefaults(b => b.AddAsKeyed());

Standard Resilience Handler

AddStandardResilienceHandler() chains 5 strategies:

StrategyDefault
Rate limiter1000 concurrent requests
Total timeout30 seconds
Retry3 retries, exponential backoff with jitter
Circuit breakerOpens at 10% failure rate
Attempt timeout10 seconds per attempt
builder.Services.AddHttpClient("api")
    .AddStandardResilienceHandler(options =>
    {
        options.Retry.MaxRetryAttempts = 5;
        options.Retry.Delay = TimeSpan.FromSeconds(1);
        options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(60);
        options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(15);

        // Disable retries for non-idempotent methods
        options.Retry.DisableForUnsafeHttpMethods();
    });

DelegatingHandler for Auth Token Injection

public sealed class AuthenticationHandler(ITokenService tokenService)
    : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var token = await tokenService.GetAccessTokenAsync(cancellationToken);
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        return await base.SendAsync(request, cancellationToken);
    }
}

// Registration
builder.Services.AddTransient<AuthenticationHandler>();
builder.Services.AddHttpClient("api")
    .AddHttpMessageHandler<AuthenticationHandler>()
    .AddStandardResilienceHandler();

DelegatingHandler for Correlation ID Propagation

public sealed class CorrelationIdHandler(IHttpContextAccessor httpContextAccessor)
    : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (httpContextAccessor.HttpContext?.Request.Headers
                .TryGetValue("X-Correlation-Id", out var correlationId) is true)
        {
            request.Headers.Add("X-Correlation-Id", correlationId.ToString());
        }
        return base.SendAsync(request, cancellationToken);
    }
}

SocketsHttpHandler Configuration

builder.Services.AddHttpClient("advanced")
    .UseSocketsHttpHandler((handler, _) =>
    {
        handler.PooledConnectionLifetime = TimeSpan.FromMinutes(2);
        handler.PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1);
        handler.MaxConnectionsPerServer = 100;
        handler.AutomaticDecompression =
            DecompressionMethods.GZip | DecompressionMethods.Brotli;
    });

Testing with Mock Handler

public sealed class MockHttpHandler(
    HttpStatusCode statusCode,
    string content) : HttpMessageHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Task.FromResult(new HttpResponseMessage(statusCode)
        {
            Content = new StringContent(content, Encoding.UTF8, "application/json")
        });
    }
}

// In test
var handler = new MockHttpHandler(HttpStatusCode.OK, """{"id":1}""");
var client = new HttpClient(handler) { BaseAddress = new Uri("https://api.test/") };
var service = new MyService(client);

Anti-patterns

Don't Create HttpClient Per Request

// BAD — socket exhaustion under load, ignores DNS changes
public async Task<string> GetDataAsync()
{
    using var client = new HttpClient();
    return await client.GetStringAsync("https://api.example.com/data");
}

// GOOD — factory-managed
public async Task<string> GetDataAsync(CancellationToken ct)
{
    var client = factory.CreateClient("api");
    return await client.GetStringAsync("https://api.example.com/data", ct);
}

Don't Capture Typed Clients in Singletons

// BAD — transient HttpClient captured by singleton defeats handler rotation
services.AddSingleton<MySingletonService>();
services.AddHttpClient<MySingletonService>();

// GOOD — use keyed client or IHttpClientFactory in singletons
services.AddSingleton<MySingletonService>();
services.AddHttpClient("myservice").AddAsKeyed(ServiceLifetime.Singleton);

Don't Mutate DefaultRequestHeaders on Shared Clients

// BAD — not thread-safe
httpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", token);

// GOOD — use DelegatingHandler or per-request HttpRequestMessage
using var request = new HttpRequestMessage(HttpMethod.Get, "/api/data");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
await httpClient.SendAsync(request, ct);

Don't Forget CancellationToken

// BAD — no cancellation support
var result = await httpClient.GetFromJsonAsync<Order>("/orders/1");

// GOOD — always pass CancellationToken
var result = await httpClient.GetFromJsonAsync<Order>("/orders/1", cancellationToken);

Don't Stack Multiple Resilience Handlers

// BAD — conflicting resilience strategies
builder.AddStandardResilienceHandler();
builder.AddStandardHedgingHandler();

// GOOD — one standard handler, or a custom pipeline
builder.AddStandardResilienceHandler();

Decision Guide

ScenarioRecommendation
New .NET 10 projectKeyed clients with AddAsKeyed()
Singleton service needs HttpClientNamed client via IHttpClientFactory or keyed singleton
External API callsAddStandardResilienceHandler() on every client
Auth token injectionDelegatingHandler registered with AddHttpMessageHandler
Hedging (parallel requests)AddStandardHedgingHandler() for latency-sensitive calls
Non-idempotent methodsDisableForUnsafeHttpMethods() on retry options
Custom retry logicAddResilienceHandler("name", builder => ...)
Connection pooling controlUseSocketsHttpHandler with PooledConnectionLifetime
API client generationRefit with AddRefitClient<T>()
Integration testingCustom HttpMessageHandler or MockHttpMessageHandler

Related skills

Backend & APIsintegrationsbackend

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.