HttpClient DelegatingHandler 意外的生命周期
HttpClient DelegatingHandler unexpected life cycle
在 ASP.NET Core 2.1 应用程序中,我正在使用 HttpClient
发出 REST 请求。我正在使用 DelegatingHandler
s 来定义一些常见的行为。我在这里注册:
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>();
我将 DelegatingHandler
s 注册为 Scoped,但在两个不同的范围(请求)中我得到相同的 DelegatingHandler
。我的意思是 DelegationgHandler
的构造函数只被调用一次,并且同一实例用于更多请求(如单例)。其他一切都符合预期 - 其他服务、TypedHttpClients 和 HttpClient 的生命周期都正常。
我在构造函数中通过断点测试了所有内容,并且在每个实例中都测试了 Guid,因此我可以区分实例。
当我将 DelegatingHandler
s 注册为 Transient 时,它没有任何区别。
TL;DR DelegatingHandler
s 像单例一样解析,即使它们被注册为作用域。这让我的服务生活方式变得混乱。
经过一些调查,我发现尽管 DelegatingHandler
s 是通过依赖注入解决的,但是 DelegatingHandler
s 的生命周期有点出乎意料,需要对 HttpClientFactory
有更深入的了解.NET 核心 2.1.
HttpClientFactory
每次都会创建新的 HttpClient
,但会在多个 HttpClient
之间共享 HttpMessageHandler
。您可以在 Steve Gordon's article.
中找到更多相关信息
因为 DelegatingHandler
的实际实例保存在 HttpMessageHandler
中(在 InnerHandler
属性 中递归)并且 HttpMessageHandler
是共享的,那么 DelegatingHandler
s 与共享 HttpMessageHandler
.
以相同的方式共享并具有相同的生命周期
此处的服务提供商仅用于在 HttpClientFactory
"decides to" 时创建新的 DelegatingHandler
- 因此每个 DelegatingHandler
都必须注册为瞬态!否则你会得到不确定的行为。 HttpClientFactory
会尝试重用已经使用过的 DelegatingHandler
.
解决方法
如果你需要在DelegatingHandler
中解析依赖,你可以在构造函数中解析IHttpContextAccessor
,然后在httpContextAccessor.HttpContext.RequestServices
中通过ServiceProvider
解析依赖。
这种方法不完全是 "architecturally clean",但它是我找到的唯一解决方法。
示例:
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 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));
});
在 ASP.NET Core 2.1 应用程序中,我正在使用 HttpClient
发出 REST 请求。我正在使用 DelegatingHandler
s 来定义一些常见的行为。我在这里注册:
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>();
我将 DelegatingHandler
s 注册为 Scoped,但在两个不同的范围(请求)中我得到相同的 DelegatingHandler
。我的意思是 DelegationgHandler
的构造函数只被调用一次,并且同一实例用于更多请求(如单例)。其他一切都符合预期 - 其他服务、TypedHttpClients 和 HttpClient 的生命周期都正常。
我在构造函数中通过断点测试了所有内容,并且在每个实例中都测试了 Guid,因此我可以区分实例。
当我将 DelegatingHandler
s 注册为 Transient 时,它没有任何区别。
TL;DR DelegatingHandler
s 像单例一样解析,即使它们被注册为作用域。这让我的服务生活方式变得混乱。
经过一些调查,我发现尽管 DelegatingHandler
s 是通过依赖注入解决的,但是 DelegatingHandler
s 的生命周期有点出乎意料,需要对 HttpClientFactory
有更深入的了解.NET 核心 2.1.
HttpClientFactory
每次都会创建新的 HttpClient
,但会在多个 HttpClient
之间共享 HttpMessageHandler
。您可以在 Steve Gordon's article.
因为 DelegatingHandler
的实际实例保存在 HttpMessageHandler
中(在 InnerHandler
属性 中递归)并且 HttpMessageHandler
是共享的,那么 DelegatingHandler
s 与共享 HttpMessageHandler
.
此处的服务提供商仅用于在 HttpClientFactory
"decides to" 时创建新的 DelegatingHandler
- 因此每个 DelegatingHandler
都必须注册为瞬态!否则你会得到不确定的行为。 HttpClientFactory
会尝试重用已经使用过的 DelegatingHandler
.
解决方法
如果你需要在DelegatingHandler
中解析依赖,你可以在构造函数中解析IHttpContextAccessor
,然后在httpContextAccessor.HttpContext.RequestServices
中通过ServiceProvider
解析依赖。
这种方法不完全是 "architecturally clean",但它是我找到的唯一解决方法。
示例:
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 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));
});