HttpClient DelegatingHandler 意外生命周期

Posted

技术标签:

【中文标题】HttpClient DelegatingHandler 意外生命周期【英文标题】:HttpClient DelegatingHandler unexpected life cycle 【发布时间】:2018-11-09 09:51:58 【问题描述】:

在 ASP.NET Core 2.1 应用程序中,我使用 HttpClient 发出 REST 请求。我正在使用DelegatingHandlers 来定义一些常见的行为。我在这里注册:

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

我将DelegatingHandlers 注册为Scoped,但在两个不同的范围(请求)中我得到相同的DelegatingHandler。我的意思是 DelegationgHandler 的构造函数只被调用一次,并且在更多请求(如单例)中使用相同的实例。其他一切都如预期 - 其他服务的生命周期,TypedHttpClients 和 HttpClient 都可以。

我在构造函数中通过断点测试了所有内容,并且在每个实例中都测试了 Guid,因此我可以区分实例。

当我将DelegatingHandlers 注册为瞬态时,它没有任何区别。

TL;DR DelegatingHandlers 像单例一样被解析,即使它们被注册为作用域。这导致我在服务生活方式上大打折扣。

【问题讨论】:

【参考方案1】:

经过一番调查,我发现尽管DelegatingHandlers 是通过依赖注入解决的,但DelegatingHandlers 的生命周期有点出乎意料,需要对.NET Core 2.1 中的HttpClientFactory 有更深入的了解。

HttpClientFactory 每次都会创建新的HttpClient,但会在多个HttpClients 之间共享HttpMessageHandler。有关这方面的更多信息,您可以在Steve Gordon's article 找到。

因为DelegatingHandlers 的实际实例保存在HttpMessageHandler 中(递归地在InnerHandler 属性中)并且HttpMessageHandler 是共享的,所以DelegatingHandlers 以相同的方式共享并且具有与共享的@ 相同的生命周期987654335@.

这里的服务提供者仅用于在HttpClientFactory“决定”时创建新的DelegatingHandlers - 因此每个DelegatingHandler 都必须注册为瞬态!否则你会得到不确定的行为。 HttpClientFactory 会尝试重用已经使用过的DelegatingHandler

解决方法

如果您需要在DelegatingHandler 中解析依赖关系,您可以在构造函数中解析IHttpContextAccessor,然后在httpContextAccessor.HttpContext.RequestServices 中通过ServiceProvider 解析依赖关系。

这种方法并不完全“架构干净”,但它是我发现的唯一解决方法。

例子:

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

【讨论】:

哇,.net 3.1 中仍然是这种情况吗?目前面临一些可能由此产生的问题。在委托处理程序中,我们正在使用 DbContext,并且我们收到异常,指出新操作已启动,而另一个操作仍在进行。 别忘了注册HttpContextAccessorservices.AddHttpContextAccessor(); 这相当于:services.TryAddSingleton&lt;IHttpContextAccessor, HttpContextAccessor&gt;(); 这仅适用于 ASP.NET,无权访问 IHttpContextAccessor 的工作服务呢? 找到一篇文章,它解释了与您观察到的问题相同的问题,但更详细。放在这里分享。 andrewlock.net/…【参考方案2】:

从 .Net Core 2.2 开始,有一种替代方法不使用 IHttpContextAccessor 作为服务定位器来解决作用域依赖关系。请参阅此问题的详细信息here。

这对 .NET Core 控制台应用程序最有用:

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

【讨论】:

为什么new HttpClient?我们可以使用IHttpClientFactory 提供的客户端吗? s.GetRequiredService&lt;IHttpClientFactory&gt;().CreateClient(nameof(MyTypedClient)); IHttpClientFactory 只会创建带有 DelegatingHandler 的 HttpClient 作为单例,它违背了具有范围处理程序的解决方法的目的。你仍然需要new HttpClient,但你也可以这样做:var typedClientFactory=s.GetService&lt;ITypedHttpClientFactory&lt;MyTypedClient&gt;&gt;();typedClientFactory.CreateClient(httpClient);

以上是关于HttpClient DelegatingHandler 意外生命周期的主要内容,如果未能解决你的问题,请参考以下文章

轻松把玩HttpClient之封装HttpClient工具类,插件式配置HttpClient对象

Httpclient4.5.*HttpClient请求,对于新建httpclient实例时保持会话

HttpClient学习整理

httpclient简介说明

httpClient 保持session

httpclient post