Laurynas Lazauskas
Laurynas Lazauskas

Reputation: 3251

Add HTTP message handler to all HTTP clients

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

Answers (1)

Laurynas Lazauskas
Laurynas Lazauskas

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:

1. Using .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>());
                });
            });
    });

2. Using .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

Related Questions