Поделиться через


IHttpClientFactory с .NET

В этой статье вы узнаете, как использовать IHttpClientFactory интерфейс для создания HttpClient типов с различными основами .NET, такими как внедрение зависимостей (DI), ведение журнала и конфигурация. Тип HttpClient впервые появился в .NET Framework 4.5 в 2012 году. Иными словами, он используется уже довольно давно. HttpClient используется для выполнения HTTP-запросов и обработки ответов HTTP из веб-ресурсов, определенных Uri. При передаче интернет-трафика в большинстве случаев используется протокол HTTP.

В соответствии с современными принципами разработки приложений, основанными на лучших методиках, класс IHttpClientFactory выступает в качестве уровня абстракции для фабрики, который может создавать экземпляры HttpClient с настраиваемыми конфигурациями. Тип IHttpClientFactory впервые появился в .NET Core 2.1. В распространенных рабочих нагрузках .NET на основе HTTP можно воспользоваться преимуществами ПО промежуточного слоя для обработки устойчивых и временных сбоев.

Примечание.

Если вашему приложению требуются файлы cookie, лучше избежать использования IHttpClientFactory в приложении. Альтернативные способы управления клиентами см . в рекомендациях по использованию HTTP-клиентов.

Внимание

Управление HttpClient временем существования экземпляров, созданных IHttpClientFactory вручную, отличается от экземпляров, созданных вручную. Стратегии предназначены для использования кратковременных клиентов, созданных IHttpClientFactory или долгосрочными клиентами с PooledConnectionLifetime настройкой. Дополнительные сведения см. в разделе "Управление временем существования HttpClient" и "Рекомендации по использованию HTTP-клиентов".

Тип IHttpClientFactory.

Для всех примеров исходного кода, предоставленного Microsoft.Extensions.Http в этой статье, требуется установка пакета NuGet. Кроме того, в примерах кода демонстрируется использование HTTP-запросов GET для получения пользовательских Todo объектов из бесплатного API заполнителя {JSON}.

При вызове любого из методов расширения AddHttpClient вы добавляете IHttpClientFactory и связанные службы в IServiceCollection. Тип IHttpClientFactory предоставляет следующие преимущества:

  • Предоставление класса HttpClient в качестве типа, готового к внедрению зависимостей.
  • Центральное расположение для именования и настройки логических экземпляров HttpClient.
  • Кодификация концепции исходящего ПО промежуточного слоя путем делегирования обработчиков в HttpClient.
  • Предоставление методов расширений для ПО промежуточного слоя на основе Polly для делегирования обработчиков в HttpClient.
  • Управляет кэшированием и временем существования базовых HttpClientHandler экземпляров. Автоматическое управление позволяет избежать обычных проблем со службой доменных имен (DNS), которые возникают при управлении временем существования HttpClient вручную.
  • Настройка параметров ведения журнала (через ILogger) для всех запросов, отправленных через клиентов, созданных фабрикой.

Шаблоны потребления

Существует несколько способов использования IHttpClientFactory в приложении:

Оптимальный подход зависит от требований приложения.

Базовое использование

Чтобы зарегистрировать IHttpClientFactory, вызовите AddHttpClient:

using Shared;
using BasicHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHttpClient();
builder.Services.AddTransient<TodoService>();

using IHost host = builder.Build();

Использование служб может потребовать IHttpClientFactory в качестве параметра конструктора с внедрением зависимостей. Следующий код использует IHttpClientFactory для создания экземпляра HttpClient:

using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;

namespace BasicHttp.Example;

public sealed class TodoService(
    IHttpClientFactory httpClientFactory,
    ILogger<TodoService> logger)
{
    public async Task<Todo[]> GetUserTodosAsync(int userId)
    {
        // Create the client
        using HttpClient client = httpClientFactory.CreateClient();
        
        try
        {
            // Make HTTP GET request
            // Parse JSON response deserialize into Todo types
            Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
                $"https://jsonplaceholder.typicode.com/todos?userId={userId}",
                new JsonSerializerOptions(JsonSerializerDefaults.Web));

            return todos ?? [];
        }
        catch (Exception ex)
        {
            logger.LogError("Error getting something fun to say: {Error}", ex);
        }

        return [];
    }
}

Подобное использование IHttpClientFactory — это хороший способ рефакторинга имеющегося приложения. Он не оказывает влияния на использование HttpClient. Там, где в существующем приложении создаются экземпляры HttpClient, используйте вызовы к CreateClient.

Именованные клиенты

Именованные клиенты являются хорошим выбором в следующих случаях:

  • Приложение требует много отдельных использований HttpClient.
  • Многие HttpClient экземпляры имеют разные конфигурации.

Конфигурацию именованного HttpClient можно указать во время регистрации в IServiceCollection:

using Shared;
using NamedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

string? httpClientName = builder.Configuration["TodoHttpClientName"];
ArgumentException.ThrowIfNullOrEmpty(httpClientName);

builder.Services.AddHttpClient(
    httpClientName,
    client =>
    {
        // Set the base address of the named client.
        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");

        // Add a user-agent default request header.
        client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
    });

В приведенном выше коде клиент регистрируется со следующими сведениями:

  • именем, извлеченным из конфигурации в "TodoHttpClientName";
  • базовым адресом https://jsonplaceholder.typicode.com/;
  • "User-Agent".

Вы можете использовать конфигурацию для указания имен HTTP-клиентов. Это помогает избежать ошибок в именах клиентов при их добавлении и создании. В этом примере для настройки имени HTTP-клиента используется файл appsettings.json:

{
    "TodoHttpClientName": "JsonPlaceholderApi"
}

Вы можете легко расширить эту конфигурацию и сохранить дополнительные сведения о том, как будет работать клиент HTTP. Дополнительные сведения см. в статье Конфигурация в .NET.

Создание клиента

При каждом вызове CreateClient:

  • создается новый экземпляр HttpClient;
  • вызывается действие настройки.

Чтобы создать именованный клиент, передайте его имя в CreateClient:

using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Shared;

namespace NamedHttp.Example;

public sealed class TodoService
{
    private readonly IHttpClientFactory _httpClientFactory = null!;
    private readonly IConfiguration _configuration = null!;
    private readonly ILogger<TodoService> _logger = null!;

    public TodoService(
        IHttpClientFactory httpClientFactory,
        IConfiguration configuration,
        ILogger<TodoService> logger) =>
        (_httpClientFactory, _configuration, _logger) =
            (httpClientFactory, configuration, logger);

    public async Task<Todo[]> GetUserTodosAsync(int userId)
    {
        // Create the client
        string? httpClientName = _configuration["TodoHttpClientName"];
        using HttpClient client = _httpClientFactory.CreateClient(httpClientName ?? "");

        try
        {
            // Make HTTP GET request
            // Parse JSON response deserialize into Todo type
            Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
                $"todos?userId={userId}",
                new JsonSerializerOptions(JsonSerializerDefaults.Web));

            return todos ?? [];
        }
        catch (Exception ex)
        {
            _logger.LogError("Error getting something fun to say: {Error}", ex);
        }

        return [];
    }
}

В приведенном выше коде в HTTP-запросе не требуется указывать имя узла. Достаточно передать только путь, так как используется базовый адрес, заданный для клиента.

Типизированные клиенты

Типизированные клиенты:

  • предоставляют те же возможности, что и именованные клиенты, без необходимости использовать строки в качестве ключей.
  • Это помогает IntelliSense и компилятору при использовании клиентов.
  • Они предоставляют единое расположение для настройки и взаимодействия с конкретным клиентом HttpClient. Например, можно использовать один типизированный клиент:
    • для одной серверной конечной точки;
    • для инкапсуляции всей логики, связанной с конечной точкой.
  • Поддерживаются работа с внедрением зависимостей и возможность вставки в нужное место в приложении.

Типизированный клиент принимает параметр HttpClient в конструкторе:

using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;

namespace TypedHttp.Example;

public sealed class TodoService(
    HttpClient httpClient,
    ILogger<TodoService> logger) : IDisposable
{
    public async Task<Todo[]> GetUserTodosAsync(int userId)
    {
        try
        {
            // Make HTTP GET request
            // Parse JSON response deserialize into Todo type
            Todo[]? todos = await httpClient.GetFromJsonAsync<Todo[]>(
                $"todos?userId={userId}",
                new JsonSerializerOptions(JsonSerializerDefaults.Web));

            return todos ?? [];
        }
        catch (Exception ex)
        {
            logger.LogError("Error getting something fun to say: {Error}", ex);
        }

        return [];
    }

    public void Dispose() => httpClient?.Dispose();
}

В предыдущем коде:

  • Конфигурация задается при добавлении типизированного клиента в коллекцию служб.
  • HttpClient назначается как переменная (поле) с областью видимости класса и используется с предоставляемыми API.

Можно создать связанные с API методы, которые предоставляют функциональные возможности HttpClient. Например, GetUserTodosAsync метод инкапсулирует код для извлечения объектов, относящихся Todo к пользователю.

Следующий код вызывает AddHttpClient регистрацию типизированного клиентского класса:

using Shared;
using TypedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHttpClient<TodoService>(
    client =>
    {
        // Set the base address of the typed client.
        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");

        // Add a user-agent default request header.
        client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
    });

Типизированный клиент регистрируется во внедрении зависимостей как временный. В приведенном выше коде AddHttpClient регистрирует TodoService как временную службу. Эта регистрация использует фабричный метод для следующих задач:

  1. Создайте экземпляр HttpClient.
  2. Создайте экземпляр TodoService, передав его конструктору экземпляр HttpClient.

Внимание

Использование типизированных клиентов в одноэлементных службах может быть опасным. Дополнительные сведения см . в разделе "Избегание типизированных клиентов" в разделе " Службы с одним типом".

Примечание.

При регистрации типизированного клиента с AddHttpClient<TClient> помощью метода тип должен иметь конструктор, TClient который принимает в HttpClient качестве параметра. Кроме того, TClient тип не должен быть зарегистрирован в контейнере DI отдельно, так как это приведет к последующей регистрации перезаписи бывшего.

Созданные клиенты

IHttpClientFactory можно использовать в сочетании с библиотеками сторонних разработчиков, например Refit. Refit — это библиотека REST для .NET. Она поддерживает декларативные определения REST API, сопоставляя методы интерфейса с конечными точками. Реализация интерфейса формируется динамически с помощью RestService с использованием HttpClient для совершения внешних вызовов HTTP.

Рассмотрим следующий record тип:

namespace Shared;

public record class Todo(
    int UserId,
    int Id,
    string Title,
    bool Completed);

В следующем примере, который является простым интерфейсом, используется пакет NuGet Refit.HttpClientFactory:

using Refit;
using Shared;

namespace GeneratedHttp.Example;

public interface ITodoService
{
    [Get("/todos?userId={userId}")]
    Task<Todo[]> GetUserTodosAsync(int userId);
}

Предыдущий интерфейс C#:

  • Определяет метод с именем GetUserTodosAsync, который возвращает экземпляр Task<Todo[]>.
  • Объявляет для внешнего API атрибут Refit.GetAttribute с путем и строкой запроса.

Можно добавить типизированный клиент, используя Refit для создания реализации:

using GeneratedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Refit;
using Shared;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddRefitClient<ITodoService>()
    .ConfigureHttpClient(client =>
    {
        // Set the base address of the named client.
        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");

        // Add a user-agent default request header.
        client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
    });

При необходимости можно использовать определенный интерфейс с реализацией, предоставленной с помощью внедрения зависимостей и Refit.

Выполнение запросов POST, PUT и DELETE

В предыдущих примерах все HTTP-запросы используют GET http-команду. HttpClient также поддерживает другие HTTP-команды, в том числе:

  • POST
  • PUT
  • DELETE
  • PATCH

Полный список поддерживаемых HTTP-команд см. в статье HttpMethod. Дополнительные сведения о выполнении HTTP-запросов см. в статье "Отправка запроса с помощью HttpClient".

В следующем примере показано, как выполнить HTTP-запрос POST :

public async Task CreateItemAsync(Item item)
{
    using StringContent json = new(
        JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
        Encoding.UTF8,
        MediaTypeNames.Application.Json);

    using HttpResponseMessage httpResponse =
        await httpClient.PostAsync("/api/items", json);

    httpResponse.EnsureSuccessStatusCode();
}

В приведенном выше коде метод CreateItemAsync выполняет следующие задачи:

  • сериализует параметр Item в JSON с помощью System.Text.Json. Для настройки процесса сериализации используется экземпляр JsonSerializerOptions.
  • создает экземпляр StringContent для упаковки сериализованного JSON для отправки в тексте HTTP-запроса.
  • вызывает метод PostAsync для отправки содержимого JSON по указанному URL-адресу. Это относительный URL-адрес, который добавляется в свойство HttpClient.BaseAddress.
  • вызывает метод EnsureSuccessStatusCode, чтобы создавать исключение, если код состояния ответа означает неудачное выполнение.

HttpClient также поддерживает другие типы содержимого. Например, MultipartContent и StreamContent. Полный список поддерживаемого содержимого см. в статье HttpContent.

В следующем примере показан HTTP-запрос PUT :

public async Task UpdateItemAsync(Item item)
{
    using StringContent json = new(
        JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
        Encoding.UTF8,
        MediaTypeNames.Application.Json);

    using HttpResponseMessage httpResponse =
        await httpClient.PutAsync($"/api/items/{item.Id}", json);

    httpResponse.EnsureSuccessStatusCode();
}

Приведенный выше код очень похож на POST пример. Метод UpdateItemAsync вызывает PutAsync вместо PostAsync.

В следующем примере показан HTTP-запрос DELETE :

public async Task DeleteItemAsync(Guid id)
{
    using HttpResponseMessage httpResponse =
        await httpClient.DeleteAsync($"/api/items/{id}");

    httpResponse.EnsureSuccessStatusCode();
}

В приведенном выше коде метод DeleteItemAsync вызывает DeleteAsync. Поскольку HTTP-запросы DELETE обычно не содержат текст, метод DeleteAsync не предоставляет перегрузку, которая принимает экземпляр HttpContent.

Дополнительные сведения об использовании различных HTTP-команд с HttpClient см. в статье HttpClient.

Управление жизненным циклом HttpClient

При каждом вызове CreateClient в IHttpClientFactory возвращается новый экземпляр HttpClient. Один HttpClientHandler экземпляр создается на имя клиента. Фабрика обеспечивает управление временем существования экземпляров HttpClientHandler.

IHttpClientFactory кэширует HttpClientHandler экземпляры, созданные фабрикой для уменьшения потребления ресурсов. Экземпляр HttpClientHandler может быть повторно использован из кэша при создании нового HttpClient экземпляра, если срок его существования не истек.

Кэширование обработчиков желательно, так как каждый обработчик обычно управляет собственным базовым пулом http-подключений. Создание дополнительных обработчиков может привести к нехватке сокета и задержкам подключения. Некоторые обработчики поддерживают подключения открытыми в течение неопределенного периода, что может помешать обработчику отреагировать на изменения DNS.

Время существования обработчика по умолчанию — две минуты. Чтобы переопределить значение по умолчанию, вызовите для каждого клиента SetHandlerLifetime в IServiceCollection:

services.AddHttpClient("Named.Client")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

Внимание

HttpClient экземпляры, созданные с помощью IHttpClientFactory , предназначены для кратковременной жизни.

  • Повторное использование и повторное HttpMessageHandlerвосстановление после истечения срока их существования является важным для IHttpClientFactory обеспечения реагирования обработчиков на изменения DNS. HttpClient привязан к конкретному экземпляру обработчика при его создании, поэтому новые HttpClient экземпляры должны быть своевременно запрошены, чтобы клиент получил обновленный обработчик.

  • Удаление таких HttpClient экземпляров, созданных фабрикой, не приведет к исчерпанию сокета, так как его удаление не приведет к удалениюHttpMessageHandler. IHttpClientFactory отслеживает и удаляет ресурсы, используемые для создания HttpClient экземпляров, в частности HttpMessageHandler экземпляров, как только срок их существования истекает, и они больше не HttpClient используются.

Сохранение одного HttpClient экземпляра в живых в течение длительного времени является общим шаблоном, который можно использовать в качестве альтернативыIHttpClientFactory, однако для этого шаблона требуется дополнительная настройка, напримерPooledConnectionLifetime. Вы можете использовать либо долгоживущие клиенты с PooledConnectionLifetimeклиентами, созданными в течение длительного времени, либо краткосрочными IHttpClientFactoryклиентами. Сведения о том, какую стратегию следует использовать в приложении, см. в рекомендациях по использованию HTTP-клиентов.

Настройка HttpMessageHandler

Иногда необходимо контролировать конфигурацию внутреннего обработчика HttpMessageHandler, используемого клиентом.

При добавлении именованного или типизированного клиента возвращается IHttpClientBuilder. Для определения делегата в IServiceCollection можно использовать метод расширения ConfigurePrimaryHttpMessageHandler. Делегат используется для создания и настройки основного обработчика HttpMessageHandler, используемого этим клиентом:

.ConfigurePrimaryHttpMessageHandler(() =>
{
    return new HttpClientHandler
    {
        AllowAutoRedirect = false,
        UseDefaultCredentials = true
    };
});

Настройка HttClientHandler позволяет указать прокси-сервер для HttpClient экземпляра среди различных других свойств обработчика. Дополнительные сведения см. в разделе "Прокси-сервер для каждого клиента".

Дополнительная настройка

Существует несколько дополнительных вариантов настройки для управления IHttpClientHandler:

Метод Description
AddHttpMessageHandler Добавляет дополнительный обработчик сообщений для именованного объекта HttpClient.
AddTypedClient Настраивает привязку между TClient и именованным объектом HttpClient, связанным с IHttpClientBuilder.
ConfigureHttpClient Добавляет делегат, который будет использоваться для настройки именованного HttpClient.
ConfigurePrimaryHttpMessageHandler Настраивает основной обработчик сообщений HttpMessageHandler из контейнера внедрения зависимостей для именованного объекта HttpClient.
RedactLoggedHeaders Задает коллекцию имен заголовков HTTP, для которых значения должны быть исправлены перед записью в журнал.
SetHandlerLifetime Задает период времени, в течение которого экземпляр HttpMessageHandler может использоваться повторно. Для каждого именованного клиента можно указать свое значение времени существования настроенного обработчика.
UseSocketsHttpHandler Настраивает новый или ранее добавленный SocketsHttpHandler экземпляр из контейнера внедрения зависимостей, который будет использоваться в качестве основного обработчика именованного HttpClient. (только .NET 5+ )

Использование IHttpClientFactory вместе с SocketsHttpHandler

Реализация SocketsHttpHandler добавлена HttpMessageHandler в .NET Core 2.1, которая позволяет PooledConnectionLifetime настроить. Этот параметр используется для обеспечения реагирования обработчика на изменения DNS, поэтому использование SocketsHttpHandler считается альтернативой использованию IHttpClientFactory. Дополнительные сведения см. в руководстве по использованию HTTP-клиентов.

SocketsHttpHandler Однако вместе IHttpClientFactory можно использовать и улучшить настройку. Используя оба этих API, можно воспользоваться возможностью настройки на низком уровне (например, при LocalCertificateSelectionCallback использовании динамического выбора сертификатов) и на высоком уровне (например, с использованием интеграции DI и нескольких конфигураций клиента).

Чтобы использовать оба API:

  1. Укажите SocketsHttpHandler как PrimaryHandler через ConfigurePrimaryHttpMessageHandlerили UseSocketsHttpHandler (только .NET 5+).
  2. Настройка SocketsHttpHandler.PooledConnectionLifetime на основе интервала, в который требуется обновить DNS; например, до значения, которое было ранее в HandlerLifetime.
  3. (Необязательно) Так как SocketsHttpHandler будет обрабатывать пул подключений и переработку, обработчик повторно используется на IHttpClientFactory уровне больше не требуется. Ее можно отключить, задав для параметра HandlerLifetime значение Timeout.InfiniteTimeSpan.
services.AddHttpClient(name)
    .UseSocketsHttpHandler((handler, _) =>
        handler.PooledConnectionLifetime = TimeSpan.FromMinutes(2)) // Recreate connection every 2 minutes
    .SetHandlerLifetime(Timeout.InfiniteTimeSpan); // Disable rotation, as it is handled by PooledConnectionLifetime

В приведенном выше примере 2 минуты были выбраны произвольно в целях иллюстрации, выравнивая значение по умолчанию HandlerLifetime . Вы должны выбрать значение на основе ожидаемой частоты ИЗМЕНЕНИЙ DNS или других сетевых изменений. Дополнительные сведения см . в разделе "Поведение DNS" в HttpClient рекомендациях и разделе "Примечания" в PooledConnectionLifetime документации по API.

Избегайте типизированных клиентов в одноэлементных службах

При использовании именованного подхода IHttpClientFactory клиента внедряется в службы, а HttpClient экземпляры создаются при каждом вызове HttpClient CreateClient при каждом необходимости.

Однако с типизированным подходом клиента типизированные клиенты являются временными объектами, которые обычно внедряются в службы. Это может привести к проблеме, так как типизированный клиент может быть внедрен в одну службу.

Внимание

Ожидается, что типизированные клиенты будут короткими в том же смысле, что HttpClient и экземпляры, созданные IHttpClientFactory (дополнительные сведения см. в разделеHttpClient "Управление временем существования"). После создания IHttpClientFactory типизированного экземпляра клиента он не контролирует. Если типизированный экземпляр клиента фиксируется в одном элементе, он может предотвратить реагирование на изменения DNS, победив одно из целей IHttpClientFactory.

Если необходимо использовать HttpClient экземпляры в одной службе, рассмотрите следующие варианты:

  • Вместо этого используйте именованный подход клиента , внедряя IHttpClientFactory одноэлементную службу и повторно создавая HttpClient экземпляры при необходимости.
  • Если требуется типизированный подход клиента , используйте SocketsHttpHandler его PooledConnectionLifetime в качестве основного обработчика. Дополнительные сведения об использовании SocketsHttpHandler IHttpClientFactoryс помощью см. в разделе "Использование IHttpClientFactory вместе с SocketsHttpHandler".

Области обработчика сообщений в IHttpClientFactory

IHttpClientFactory создает отдельную область DI для каждого HttpMessageHandler экземпляра. Эти области di отделены от областей di приложения (например, ASP.NET области входящих запросов или созданной пользователем области DI), поэтому они не будут совместно использовать экземпляры служб с областью действия. Области обработчика сообщений привязаны к времени существования обработчика и могут выходить за пределы областей приложений, что может привести к повторному использовании одного HttpMessageHandler экземпляра с одинаковыми внедренными зависимостями области между несколькими входящими запросами.

Схема с двумя областями di приложения и отдельной областью обработчика сообщений

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

Если вам требуется доступ к области di приложения из обработчика сообщений, для проверки подлинности в качестве примера вы инкапсулируете логику с поддержкой областей в отдельном DelegatingHandlerвременном режиме и заключите его вокруг HttpMessageHandler экземпляра из кэша IHttpClientFactory . Чтобы получить доступ к вызову IHttpMessageHandlerFactory.CreateHandler обработчика для любого зарегистрированного именованного клиента. В этом случае вы создадите HttpClient экземпляр самостоятельно с помощью созданного обработчика.

Схема получения доступа к областям di приложения с помощью отдельного обработчика временных сообщений и IHttpMessageHandlerFactory

В следующем примере показано создание HttpClient с учетом DelegatingHandlerобласти:

if (scopeAwareHandlerType != null)
{
    if (!typeof(DelegatingHandler).IsAssignableFrom(scopeAwareHandlerType))
    {
        throw new ArgumentException($"""
            Scope aware HttpHandler {scopeAwareHandlerType.Name} should
            be assignable to DelegatingHandler
            """);
    }

    // Create top-most delegating handler with scoped dependencies
    scopeAwareHandler = (DelegatingHandler)_scopeServiceProvider.GetRequiredService(scopeAwareHandlerType); // should be transient
    if (scopeAwareHandler.InnerHandler != null)
    {
        throw new ArgumentException($"""
            Inner handler of a delegating handler {scopeAwareHandlerType.Name} should be null.
            Scope aware HttpHandler should be registered as Transient.
            """);
    }
}

// Get or create HttpMessageHandler from HttpClientFactory
HttpMessageHandler handler = _httpMessageHandlerFactory.CreateHandler(name);

if (scopeAwareHandler != null)
{
    scopeAwareHandler.InnerHandler = handler;
    handler = scopeAwareHandler;
}

HttpClient client = new(handler);

Дополнительное обходное решение может выполняться с помощью метода расширения для регистрации параметров с учетом DelegatingHandler области и переопределения регистрации по умолчанию IHttpClientFactory временной службой с доступом к текущей области приложения:

public static IHttpClientBuilder AddScopeAwareHttpHandler<THandler>(
    this IHttpClientBuilder builder) where THandler : DelegatingHandler
{
    builder.Services.TryAddTransient<THandler>();
    if (!builder.Services.Any(sd => sd.ImplementationType == typeof(ScopeAwareHttpClientFactory)))
    {
        // Override default IHttpClientFactory registration
        builder.Services.AddTransient<IHttpClientFactory, ScopeAwareHttpClientFactory>();
    }

    builder.Services.Configure<ScopeAwareHttpClientFactoryOptions>(
        builder.Name, options => options.HttpHandlerType = typeof(THandler));

    return builder;
}

Дополнительные сведения см. в полном примере.

См. также