IHttpClientFactory с .NET

В этой статье вы узнаете, как создавать IHttpClientFactoryHttpClient типы с различными основами .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.

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

При вызове любого из методов расширения 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 отдельно.

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

Именованные клиенты и типизированные клиенты имеют свои собственные сильные и слабые стороны. Существует способ объединить эти два типа клиентов, чтобы получить лучшее из обоих миров.

Основным вариантом использования является следующее: используйте один и тот же типизированный клиент, но для разных доменов. Например, у вас есть первичная и вторичная служба, и они предоставляют одинаковые функциональные возможности. Это означает, что вы можете использовать тот же типизированный клиент для упаковки HttpClient использования для выдачи запросов, обработки ответов и обработки ошибок. Тот же код будет использоваться, но с различными конфигурациями (например, с разными базовыми адресами, временем ожидания и учетными данными).

В следующем примере используется тот же TodoService типизированный клиент, который был показан в разделе типизированных клиентов .

Сначала зарегистрируйте именованные и типизированные клиенты.

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

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHttpClient<TodoService>("primary"
    client =>
    {
        // Configure the primary typed client
        client.BaseAddress = new Uri("https://primary-host-address.com/");
        client.Timeout = TimeSpan.FromSeconds(3);
    });

// Register the same typed client but with different settings
builder.Services.AddHttpClient<TodoService>("secondary"
    client =>
    {
        // Configure the secondary typed client
        client.BaseAddress = new Uri("https://secondary-host-address.com/");
        client.Timeout = TimeSpan.FromSeconds(10);
    });

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

  • Первый AddHttpClient вызов регистрирует типизированный TodoService клиент под primary именем. Базовые HttpClient точки основной службы и имеют короткое время ожидания.
  • Второй AddHttpClient вызов регистрирует типизированный TodoService клиент под secondary именем. Базовые HttpClient точки для вторичной службы и имеют более длительное время ожидания.
using IHost host = builder.Build();

// Fetch an IHttpClientFactory instance to create a named client
IHttpClientFactory namedClientFactory =
    host.Services.GetRequiredService<IHttpClientFactory>();

// Fetch an ITypedHttpClientFactory<TodoService> instance to create a named and typed client
ITypedHttpClientFactory<TodoService> typedClientFactory  =
    host.Services.GetRequiredService<ITypedHttpClientFactory<TodoService>>();

// Create a TodoService instance against the primary host
var primaryClient = namedClientFactory.CreateClient("primary");
var todoService = typedClientFactory.CreateClient(primaryClient);

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

  • IHttpClientFactory Экземпляр извлекается из контейнера DI, чтобы иметь возможность создавать именованные клиенты с помощью его CreateClient метода.
  • Экземпляр ITypedHttpClientFactory<TodoService> извлекается из контейнера DI, чтобы иметь возможность создавать типизированные клиенты с помощью его CreateClient метода.
    • Эта CreateClient перегрузка получила имя HttpClient (с правильной конфигурацией) в качестве параметра.
    • Созданная todoService служба настроена для использования основной службы.

Примечание.

Тип IHttpClientFactory находится в System.Net.Http пространствах имен, а ITypedHttpClientFactory тип внутри Microsoft.Extensions.Http.

Внимание

Используйте класс реализации (в предыдущем примере ) TodoServiceв качестве параметра типа для ITypedHttpClientFactoryобъекта. Даже если у вас есть абстракция (например ITodoService , интерфейс), вам по-прежнему нужно использовать реализацию. Если вы случайно используете абстракцию (ITodoService), то при вызове ее CreateClient вызов вызовет InvalidOperationExceptionисключение.

try
{
    Todo[] todos = await todoService.GetUserTodosAsync(4);
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
    // The request timed out against the primary host

    // Create a TodoService instance against the secondary host
    var fallbackClient = namedClientFactory.CreateClient("secondary");
    var todoFallbackService = typedClientFactory.CreateClient(fallbackClient);

    // Issue request against the secondary host
    Todo[] todos = await todoFallbackService.GetUserTodosAsync(4);
}

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

  • Он пытается выдать запрос к первичной службе.
  • Если время ожидания запроса (занимает более 3 секунд), то он создает TaskCanceledException внутренний TimeoutException запрос.
  • При истечении времени ожидания создается новый клиент и используется, который теперь предназначен для вторичной службы.

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

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.
ConfigureHttpMessageHandlerBuilder Добавляет делегат, который будет использоваться для настройки обработчиков сообщений с помощью HttpMessageHandlerBuilder для именованного HttpClient.
ConfigurePrimaryHttpMessageHandler Настраивает основной обработчик сообщений HttpMessageHandler из контейнера внедрения зависимостей для именованного объекта HttpClient.
RedactLoggedHeaders Задает коллекцию имен заголовков HTTP, для которых значения должны быть исправлены перед записью в журнал.
SetHandlerLifetime Задает период времени, в течение которого экземпляр HttpMessageHandler может использоваться повторно. Для каждого именованного клиента можно указать свое значение времени существования настроенного обработчика.

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

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

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

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

  1. Укажите SocketsHttpHandler как PrimaryHandler и настройте его PooledConnectionLifetime (например, значение, которое было ранее в HandlerLifetime).
  2. Как SocketsHttpHandler и для обработки пула подключений и переработки, а затем обработчик переработки на IHttpClientFactory уровне больше не нужен. Ее можно отключить, задав для параметра HandlerLifetime значение Timeout.InfiniteTimeSpan.
services.AddHttpClient(name)
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new SocketsHttpHandler()
        {
            PooledConnectionLifetime = TimeSpan.FromMinutes(2)
        };
    })
    .SetHandlerLifetime(Timeout.InfiniteTimeSpan); // Disable rotation, as it is handled by PooledConnectionLifetime

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

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

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

Внимание

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

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

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

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

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

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

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

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

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

В следующем примере показано создание с поддержкой HttpClientDelegatingHandlerобласть:

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;
}

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

См. также