HttpClient DelegatingHandler unexpected life cycle

瘦欲@ 提交于 2020-12-04 19:57:30

问题


In ASP.NET Core 2.1 app I am making REST request using HttpClient. I am using DelegatingHandlers to define some common behavior. The registration i here:

private static void AddRestServiceDataClient<TTypedHttpClient, TTypedHttpClientImpl>(this IServiceCollection services)
    where TTypedHttpClient : class
    where TTypedHttpClientImpl : RestServiceDataClient, TTypedHttpClient
{
    var httpClientBuilder = services
        .AddHttpClient<TTypedHttpClient, TTypedHttpClientImpl>()
        .AddHttpMessageHandler<CachingHttpDelegatingHandler>()
        .AddHttpMessageHandler<ExceptionHttpDelegatingHandler>()
        .AddHttpMessageHandler<LoggingHttpDelegatingHandler>();
}

...

// EDIT: You should always register DelegatingHandlers as TRANSIENT (read answer for more).
services.AddScoped<ExceptionHttpDelegatingHandler>();
services.AddScoped<CachingHttpDelegatingHandler>();
services.AddScoped<LoggingHttpDelegatingHandler>();

I register DelegatingHandlers as Scoped, but in two different scopes (requests) I get the same DelegatingHandler. I mean that the constructor of DelegationgHandler is being called only once and the same instance is used across more requests (like singleton). Everything else is as expected - life cycle of other services, TypedHttpClients and HttpClient is ok.

I tested everything by breakpoint in constructor and I have testing Guid in every instance so I can distinguish instances.

When I register DelegatingHandlers as Transient it makes no difference.

TL;DR DelegatingHandlers are resolved like singleton even though they are registered as scoped. And this causes me mishmash in service lifestyles.


回答1:


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



回答2:


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));
}); 


来源:https://stackoverflow.com/questions/53223411/httpclient-delegatinghandler-unexpected-life-cycle

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!