Dot-Net

HttpClient DelegatingHandler 意外生命週期

  • March 25, 2019

在 ASP.NET Core 2.1 應用程序中,我使用HttpClient. 我使用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 註冊為 Transient 時,它沒有任何區別。

TL;DR DelegatingHandler像單例一樣被解析,即使它們被註冊為作用域。這導致我在服務生活方式上大打折扣。

經過一番調查,我發現儘管DelegatingHandlers 是通過依賴注入解決的,但 s 的生命週期DelegatingHandler有點出乎意料,需要對HttpClientFactory.NET Core 2.1 有更深入的了解。

HttpClientFactory``HttpClient每次都創建新的,但HttpMessageHandler在多個HttpClients 之間共享。有關這方面的更多資訊,您可以在Steve Gordon 的文章中找到。

因為DelegatingHandlers 的實際實例保存在內部(在屬性HttpMessageHandler中遞歸)並且是共享的,所以s 以相同的方式共享,並且具有與 shared 相同的生命週期。InnerHandler``HttpMessageHandler``DelegatingHandler``HttpMessageHandler

服務提供者在這裡僅用於在“決定”DelegatingHandler時創建新的 s - 因此每個都必須註冊為瞬態!否則你會得到不確定的行為。會嘗試重用已經使用過的 .HttpClientFactory``DelegatingHandler``HttpClientFactory``DelegatingHandler

解決方法

如果您需要解決依賴關係,DelegatingHandler您可以IHttpContextAccessor在建構子中解析,然後通過ServiceProviderin解析依賴關係httpContextAccessor.HttpContext.RequestServices

這種方法並不完全是“架構乾淨”,但它是我發現的唯一解決方法。

例子:

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

引用自:https://stackoverflow.com/questions/53223411