HttpClient DelegatingHandler unexpected life cycle

After some investigation I found out that even though DelegatingHandlers are resolved by dependecy injection, the lifecycle of DelegatingHandlers is a bit unexpected and requires deeper knowledge of HttpClientFactory in .NET Core 2.1.

HttpClientFactory creates new HttpClient every time, but shares HttpMessageHandler across multiple HttpClients. More information about this you can find in Steve Gordon's article.

Because actual instances of DelegatingHandlers are held inside HttpMessageHandler (recursively in InnerHandler property) and HttpMessageHandler is shared, then DelegatingHandlers are shared the same way and have same lifecycle as the shared HttpMessageHandler.

Service provider is here used only for creating new DelegatingHandlers when HttpClientFactory "decides to" - thus every DelegatingHandler must be registered as transient! Otherwise you would get non-deterministic behavior. HttpClientFactory would try to reuse already used DelegatingHandler.

Workaround

If you need to resolve dependencies in DelegatingHandler you can resolve IHttpContextAccessor in constructor and then resolve dependencies by ServiceProvider in httpContextAccessor.HttpContext.RequestServices.

This approach is not exactly "architecturally clean" but it is the only workaround I have found.

Example:

internal class MyDelegatingHandler : DelegatingHandler
{
    private readonly IHttpContextAccessor httpContextAccessor;

    protected MyDelegatingHandler(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var serviceProvider = this.httpContextAccessor.HttpContext.RequestServices;
        var myService = serviceProvider.GetRequiredService<IMyService>();
        ...
    }
}

Starting with .Net Core 2.2 there is an alternative without using IHttpContextAccessor as a service locator to resolve scoped dependencies. See the details on this issue here.

This is most useful for .NET Core console apps:

// configure a client pipeline with the name "MyTypedClient"
...

// then
services.AddTransient<MyTypedClient>((s) =>
{
    var factory = s.GetRequiredService<IHttpMessageHandlerFactory>();
    var handler = factory.CreateHandler(nameof(MyTypedClient));

    var otherHandler = s.GetRequiredService<MyOtherHandler>();
    otherHandler.InnerHandler = handler;
    return new MyTypedClient(new HttpClient(otherHandler, disposeHandler: false));
});