HttpClient DelegatingHandler неожиданный жизненный цикл - PullRequest
0 голосов
/ 09 ноября 2018

В приложении ASP.NET Core 2.1 я делаю REST-запрос, используя HttpClient. Я использую 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 вызывается только один раз, и один и тот же экземпляр используется для большего количества запросов (например, singleton). Все остальное как и ожидалось - жизненный цикл других сервисов, TypedHttpClients и HttpClient в порядке.

Я тестировал все по точке останова в конструкторе, и я тестировал Guid в каждом экземпляре, чтобы я мог различать экземпляры.

Когда я регистрирую DelegatingHandler s как Transient, это не имеет значения.

TL; DR DelegatingHandler s разрешаются как одиночные, даже если они зарегистрированы как области действия. И это приводит меня к путанице в образе жизни.

Ответы [ 3 ]

0 голосов
/ 09 ноября 2018

После некоторого исследования я обнаружил, что, хотя DelegatingHandler s разрешается путем внедрения зависимостей, жизненный цикл DelegatingHandler s немного неожидан и требует более глубокого знания HttpClientFactory в .NET Core 2.1.

HttpClientFactory создает новый HttpClient каждый раз, но делит HttpMessageHandler на несколько HttpClient с. Больше информации об этом вы можете найти в статье Стива Гордона .

Поскольку фактические экземпляры DelegatingHandler s хранятся внутри HttpMessageHandler (рекурсивно в свойстве InnerHandler) и HttpMessageHandler совместно используется, то DelegatingHandler s совместно используются таким же образом и имеют тот же жизненный цикл, что и совместно используемые HttpMessageHandler.

Поставщик услуг здесь используется только для создания новых DelegatingHandler s, когда HttpClientFactory «решает» - таким образом, каждый DelegatingHandler должен быть зарегистрирован как временный! В противном случае вы получите недетерминированное поведение. HttpClientFactory попытается повторно использовать уже использованный DelegatingHandler.

Обход

Если вам нужно разрешить зависимости в DelegatingHandler, вы можете разрешить IHttpContextAccessor в конструкторе, а затем разрешить зависимости на ServiceProvider в httpContextAccessor.HttpContext.RequestServices.

Этот подход не совсем "архитектурно чистый", но это единственный обходной путь, который я нашел.

* +1040 * Пример: * * тысяча сорок один
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>();
        ...
    }
}
0 голосов
/ 25 марта 2019

Начиная с .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));
}); 
0 голосов
/ 09 ноября 2018

Официальная документация гласит:

При использовании службы с определенными областями в промежуточном программном обеспечении внедрите службу в метод Invoke или InvokeAsync. Не внедрить через конструктор, потому что это заставляет сервис вести себя как одиночка. Для получения дополнительной информации см. ASP.NET Core Middleware.

Я думаю, что это именно ваш случай

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...