HttpClient DelegatingHandler意外的生命周期

Mar*_*lek 8 .net dependency-injection dotnet-httpclient .net-core asp.net-core

在ASP.NET Core 2.1应用中,我正在使用进行REST请求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>();
Run Code Online (Sandbox Code Playgroud)

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

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

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

TL; DR DelegatingHandler像单例一样被解析,即使它们已注册为作用域。这使我沉迷于服务型生活方式。

Mar*_*lek 17

经过一些调查,我发现即使DelegatingHandler通过可靠注入解决了DelegatingHandlers ,s 的生命周期还是有些意外,需要对HttpClientFactory.NET Core 2.1有更深入的了解。

HttpClientFactoryHttpClient每次都创建new ,但HttpMessageHandler跨多个HttpClients 共享。您可以在Steve Gordon的文章中找到有关此信息的更多信息。

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

服务提供者仅DelegatingHandlerHttpClientFactory“决定” 时用于创建new ,因此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>();
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 哇,.net 3.1 中还是这样吗?目前面临一些可能由此产生的问题。在委托处理程序中,我们使用 DbContext,并且收到异常,表明一项新操作已启动,而另一项操作仍在进行。 (2认同)
  • 这仅适用于 ASP.NET,那么无法访问 IHttpContextAccessor 的辅助服务又如何呢? (2认同)

小智 5

从 .Net Core 2.2 开始,有一种替代方案,无需使用 IHttpContextAccessor 作为服务定位器来解析范围依赖项。请参阅此处有关此问题的详细信息。

这对于 .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));
}); 
Run Code Online (Sandbox Code Playgroud)