16

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 Answer 1

38

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>());
    });
});
Sign up to request clarification or add additional context in comments.

2 Comments

This configure all should be added before or after the AddHttpClient method?
@LuizBicalho, order should not be important as long as it is within the same ServiceCollection.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.