Add HTTP message handler to all HTTP clients

Viewed 4884

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.
    }
}
1 Answers

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.

1. Using .AddSingleton() ❌

If your handler has any dependencies (ex. IHttpContextAccessor to get current correlation id), we would like to use dependency injection to resolve it.

We could use OptionsBuilder API to get a required handler using dependency injection. Unfortunately, OptionsBuilder 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, it will be used for all configurations.
    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>());
        });
    });
});

2. Using .ConfigureAll() ✔️

The following improved answer was inspired by LostInComputer.

Add .ConfigureAll in your Startup.cs and use IServiceProvider through builder object like so:

services.ConfigureAll<HttpClientFactoryOptions>(options =>
{
    options.HttpMessageHandlerBuilderActions.Add(builder =>
    {
        builder.AdditionalHandlers.Add(builder.Services.GetRequiredService<AddHeadersHandler>());
    });
});
Related