HttpClient DelegatingHandler unexpected life cycle
After some investigation I found out that even though DelegatingHandler
s are resolved by dependecy injection, the lifecycle of DelegatingHandler
s 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 HttpClient
s. More information about this you can find in Steve Gordon's article.
Because actual instances of DelegatingHandler
s are held inside HttpMessageHandler
(recursively in InnerHandler
property) and HttpMessageHandler
is shared, then DelegatingHandler
s are shared the same way and have same lifecycle as the shared HttpMessageHandler
.
Service provider is here used only for creating new DelegatingHandler
s 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));
});