Introduction
IHttpClientFactory supports named clients with individual configurations (base address, default headers, handlers). When the named client is not resolved correctly, or when IHttpClientFactory.CreateClient(name) is called with the wrong name, the returned HttpClient uses default configuration instead of the named configuration. This causes requests to go to wrong endpoints, miss authentication headers, or bypass configured policies.
Symptoms
- Named client base address is null or default
- Default headers not applied to named client requests
CreateClient("name")returns unconfigured HttpClient- Typed client does not receive the expected configuration
- Handler pipeline not applied to named client
Debug named client:
``csharp
var factory = serviceProvider.GetRequiredService<IHttpClientFactory>();
var client = factory.CreateClient("api");
Console.WriteLine($"BaseAddress: {client.BaseAddress}");
Console.WriteLine($"DefaultHeaders: {string.Join(", ", client.DefaultRequestHeaders)}");
Common Causes
- Name mismatch between
AddHttpClient("name")andCreateClient("name") - Named client registered but factory creates client with empty name
- Configuration delegate throws exception silently
- Typed client and named client mixed up
- Handler registration order wrong
Step-by-Step Fix
- 1.**Register and use named client correctly":
- 2.```csharp
- 3.// Registration in Program.cs
- 4.builder.Services.AddHttpClient("api", client =>
- 5.{
- 6.client.BaseAddress = new Uri("https://api.example.com/");
- 7.client.DefaultRequestHeaders.Add("Accept", "application/json");
- 8.client.Timeout = TimeSpan.FromSeconds(30);
- 9.})
- 10..AddHttpMessageHandler<AuthHeaderHandler>();
// Usage - inject IHttpClientFactory public class ApiService { private readonly IHttpClientFactory _clientFactory;
public ApiService(IHttpClientFactory clientFactory) { _clientFactory = clientFactory; }
public async Task<User> GetUserAsync(int id) { // WRONG - empty name returns unconfigured client // var client = _clientFactory.CreateClient();
// CORRECT - use the registered name var client = _clientFactory.CreateClient("api");
// BaseAddress is already set: https://api.example.com/ var response = await client.GetAsync($"/users/{id}"); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync<User>(); } } ```
- 1.**Use typed client instead of named client for better type safety":
- 2.```csharp
- 3.// Typed client - configuration is tied to the type, no name string needed
- 4.public class GitHubClient
- 5.{
- 6.public HttpClient HttpClient { get; }
public GitHubClient(HttpClient httpClient) { httpClient.BaseAddress = new Uri("https://api.github.com/"); httpClient.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); httpClient.DefaultRequestHeaders.Add("User-Agent", "MyApp"); HttpClient = httpClient; }
public async Task<GitHubRepo> GetRepositoryAsync(string owner, string repo) { return await HttpClient.GetFromJsonAsync<GitHubRepo>( $"/repos/{owner}/{repo}"); } }
// Registration builder.Services.AddHttpClient<GitHubClient>();
// Usage - typed client is injected directly public class RepoService { private readonly GitHubClient _gitHubClient;
public RepoService(GitHubClient gitHubClient) { _gitHubClient = gitHubClient; }
public async Task<GitHubRepo> GetRepoAsync(string owner, string repo) { return await _gitHubClient.GetRepositoryAsync(owner, repo); } } ```
- 1.**Debug named client configuration issues":
- 2.```csharp
- 3.// Verify registration at startup
- 4.var services = builder.Services;
- 5.var httpRegistrations = services
- 6..Where(s => s.ServiceType == typeof(IHttpClientBuilder))
- 7..ToList();
Console.WriteLine($"Registered HTTP clients: {httpRegistrations.Count}");
// Test named client configuration using var scope = app.Services.CreateScope(); var factory = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>();
var client = factory.CreateClient("api"); if (client.BaseAddress == null) { Console.WriteLine("WARNING: Named client 'api' has no BaseAddress!"); }
// List all registered handler types var handlers = app.Services.GetServices<DelegatingHandler>(); Console.WriteLine($"Registered handlers: {string.Join(", ", handlers.Select(h => h.GetType().Name))}"); ```
Prevention
- Prefer typed clients over named clients for compile-time safety
- Use constants for client names:
public const string ApiClient = "api"; - Test named client configuration at application startup
- Add health check that verifies HTTP client connectivity
- Document all named clients and their configurations
- Use
AddHttpClient<TClient>()with constructor injection for the best experience