Reputation: 3251
I have an HTTP message handler named AddHeadersHandler
, which extends System.Net.Http.DelegatingHandler
and I need it to be added to all current and future HttpClient
instances, including typed, named and non-named clients.
I know I can add a handler using .AddHttpMessageHandler<AddHeadersHandler>()
for a specific client, but how do I add it to all clients?
// AddHeadersHandler.cs
public class AddHeadersHandler: DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.TryAddWithoutValidation("X-Correlation-Id", Guid.NewGuid().ToString());
return base.SendAsync(request, cancellationToken);
}
}
// Startup.cs
services
.AddHttpContextAccessor()
.AddTransient<AddHeadersHandler>();
services
.AddHttpClient<MyClient>()
.AddHttpMessageHandler<AddHeadersHandler>(); // I don't want to specify this for each client.
// MyClient.cs
public class MyClient
{
public HttpClient HttpClient { get; }
public MyClient(HttpClient httpClient)
{
HttpClient = httpClient;
}
public async Task GetTest()
{
await HttpClient.GetAsync("https://localhost:5001/test"); // This should have headers attached.
}
}
Upvotes: 14
Views: 12937
Reputation: 3251
It can be done by configuring HttpClientFactoryOptions
for all named options. We need to provide a delegate in HttpMessageHandlerBuilderActions
, which will include your handler to AdditionalHandlers
property list.
There are multiple ways of doing this using the Options pattern:
.AddSingleton()
❌If your handler has any dependencies (e.g. IHttpContextAccessor
to get current correlation id), we would like to use dependency injection to resolve it.
We could use the OptionsBuilder API to get a required handler using dependency injection. Unfortunately, this API does not provide a method to configure options for all named instances like .ConfigureAll
does.
Luckily, we can get what we need by registering a factory method for IConfigureOptions<HttpClientFactoryOptions>
like so:
// Startup.cs
services.AddSingleton<IConfigureOptions<HttpClientFactoryOptions>>(
provider =>
{
// When name is null, these options will be used for all clients.
return new ConfigureNamedOptions<HttpClientFactoryOptions>(
name: null,
options =>
{
options.HttpMessageHandlerBuilderActions.Add(builder =>
{
// Here we have access to ServiceProvider
// to get an instance of the handler.
builder.AdditionalHandlers.Add(
provider.GetRequiredService<AddHeadersHandler>());
});
});
});
.ConfigureAll()
✔️The following improved answer was inspired by LostInComputer.
Add .ConfigureAll
in your Startup.cs
and use IServiceProvider
through the builder like so:
services.ConfigureAll<HttpClientFactoryOptions>(options =>
{
options.HttpMessageHandlerBuilderActions.Add(builder =>
{
builder.AdditionalHandlers.Add(builder.Services
.GetRequiredService<AddHeadersHandler>());
});
});
Upvotes: 34