Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье вы узнаете, как использовать 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. Объединение экземпляров HttpMessageHandler приводит к совместному использованию объектов CookieContainer. Непреднамеренное совместное использование CookieContainer может привести к утечке куки между несвязанными частями приложения. Кроме того, когда HandlerLifetime срок действия истекает, обработчик перезапускается, что означает, что все файлы cookie, хранящиеся в ней CookieContainer , теряются. Альтернативные способы управления клиентами см. в рекомендациях по использованию HTTP-клиентов.
Внимание
Управление временем существования экземпляров, созданных HttpClient
, совершенно отличается от управления временем существования экземпляров, созданных вручную. Стратегии предназначены для использования кратковременных клиентов, созданных IHttpClientFactory
, или долгосрочных клиентов с PooledConnectionLifetime
настройками. Дополнительные сведения см. в разделе "Управление временем существования HttpClient" и "Рекомендации по использованию HTTP-клиентов".
Тип IHttpClientFactory
.
Для всех примеров исходного кода, предоставленных в этой статье, требуется установка пакета Microsoft.Extensions.Http
NuGet. Кроме того, в примерах кода демонстрируется использование HTTP-запросов GET
для получения объектов пользовательских Todo
из бесплатного API JSON Placeholder.
При вызове любого из методов расширения 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
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"];
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
. Например, можно использовать один типизированный клиент:- для одной серверной конечной точки;
- для инкапсуляции всей логики, связанной с конечной точкой.
- Работа с внедрением зависимостей (DI) и возможность инъекции в нужные места в приложении.
Типизированный клиент принимает параметр 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)
{
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 [];
}
}
В предыдущем коде:
- Конфигурация задается при добавлении типизированного клиента в коллекцию служб.
-
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");
});
Типизированный клиент регистрируется с DI в качестве временного. В приведенном выше коде AddHttpClient
регистрирует TodoService
как временную службу. Эта регистрация использует фабричный метод для следующих задач:
- Создайте экземпляр
HttpClient
. - Создайте экземпляр
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");
});
При необходимости можно использовать заданный интерфейс с реализацией, предоставленной с помощью DI и 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
Каждый раз, когда HttpClient
вызывается на CreateClient
, возвращается новый экземпляр IHttpClientFactory
. Один HttpClientHandler экземпляр создается для каждого имени клиента. Фабрика управляет временем жизни экземпляров HttpClientHandler
.
IHttpClientFactory
кэширует экземпляры HttpClientHandler
, созданные фабрикой, для уменьшения ресурсов потребления. Экземпляр HttpClientHandler
может быть повторно использован из кэша при создании нового HttpClient
экземпляра, если срок его существования не истек.
Кэширование обработчиков желательно, так как каждый обработчик обычно управляет собственным базовым пулом http-подключений. Создание обработчиков больше, чем необходимо, может привести к исчерпанию сокетов и задержкам подключения. Некоторые обработчики поддерживают подключения открытыми в течение неопределенного периода, что может помешать обработчику отреагировать на изменения DNS.
Время существования обработчика по умолчанию — две минуты. Чтобы переопределить значение по умолчанию, вызовите SetHandlerLifetime для каждого клиента в IServiceCollection
:
services.AddHttpClient("Named.Client")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Внимание
HttpClient
экземпляры, созданные с помощью IHttpClientFactory
, предназначены для краткосрочного использования.
Важность повторного использования и воссоздания
HttpMessageHandler
после истечения срока их существования состоит в том, чтобы обеспечить реагирование обработчиков на изменения DNS дляIHttpClientFactory
.HttpClient
привязан к конкретному экземпляру обработчика при его создании, поэтому новыеHttpClient
экземпляры должны быть своевременно запрошены, чтобы клиент получил обновленный обработчик.Удаление таких
экземпляров, созданных фабрикой , не исчерпает ресурсы сокета, поскольку его удаление не приведет к удалению. IHttpClientFactory
отслеживает и удаляет ресурсы, используемые для созданияHttpClient
экземпляров, в частностиHttpMessageHandler
экземпляров, как только срок их существования истекает, и они больше неHttpClient
используются.
Сохранение одного экземпляра HttpClient
в рабочем состоянии в течение длительного времени является распространённым шаблоном, который можно использовать в качестве альтернативыIHttpClientFactory
, однако для этого шаблона требуется дополнительная настройка, например, PooledConnectionLifetime
. Вы можете использовать либо долгоживущие клиенты с PooledConnectionLifetime
, либо краткосрочные клиенты, созданные IHttpClientFactory
. Сведения о том, какую стратегию следует использовать в приложении, см. в рекомендациях по использованию HTTP-клиентов.
Настройте HttpMessageHandler
Может понадобиться контролировать конфигурацию внутреннего HttpMessageHandler, используемого клиентом.
При добавлении именованных или типизированных клиентов возвращается IHttpClientBuilder. Метод расширения ConfigurePrimaryHttpMessageHandler можно использовать для определения делегата в IServiceCollection
. Делегат используется для создания и настройки основного элемента HttpMessageHandler
, используемого данным клиентом.
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
Настройка HttClientHandler
позволяет указать прокси-сервер для HttpClient
экземпляра среди различных других свойств обработчика. Дополнительные сведения см. в разделе "Прокси-сервер для каждого клиента".
Дополнительная настройка
Существует несколько дополнительных вариантов настройки для управления IHttpClientHandler
:
Метод | Описание |
---|---|
AddHttpMessageHandler | Добавляет дополнительный обработчик сообщений для именованного объекта HttpClient . |
AddTypedClient | Настраивает привязку между TClient и именованным объектом HttpClient , связанным с IHttpClientBuilder . |
ConfigureHttpClient | Добавляет делегат, который будет использоваться для настройки именованного HttpClient . |
ConfigurePrimaryHttpMessageHandler | Настраивает основной HttpMessageHandler из контейнера внедрения зависимостей для именованного компонента HttpClient . |
RedactLoggedHeaders | Задает коллекцию имен заголовков HTTP, для которых значения должны быть скрыты перед занесением в журнал. |
SetHandlerLifetime | Задает период времени, в течение которого экземпляр HttpMessageHandler может использоваться повторно. Для каждого именованного клиента можно задать собственное значение времени жизни обработчика. |
UseSocketsHttpHandler | Настраивает новый или ранее добавленный экземпляр SocketsHttpHandler из контейнера внедрения зависимостей, который будет использоваться как основной обработчик для именованного HttpClient . (только .NET 5+ ) |
Использование IHttpClientFactory вместе с SocketsHttpHandler
Реализация SocketsHttpHandler
была добавлена в .NET Core 2.1, что позволяет настроить HttpMessageHandler
. Этот параметр используется для обеспечения реагирования обработчика на изменения DNS, поэтому использование SocketsHttpHandler
считается альтернативой использованию IHttpClientFactory
. Дополнительные сведения см. в руководстве по использованию HTTP-клиентов.
Тем не менее, SocketsHttpHandler
и IHttpClientFactory
можно использовать вместе, чтобы улучшить настраиваемость. Используя оба этих API, можно воспользоваться возможностью настройки на низком уровне (например, при LocalCertificateSelectionCallback
использовании динамического выбора сертификатов) и на высоком уровне (например, с использованием интеграции DI и нескольких конфигураций клиента).
Чтобы использовать оба API:
- Укажите
SocketsHttpHandler
какPrimaryHandler
через ConfigurePrimaryHttpMessageHandlerили UseSocketsHttpHandler (только .NET 5+). - Настройте SocketsHttpHandler.PooledConnectionLifetime в зависимости от интервала, в котором вы ожидаете обновления DNS; например, установите значение, которое было ранее в
HandlerLifetime
. - (Необязательно) Поскольку
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» в рекомендациях , а также в разделе «Примечания» в документации по API HttpClient
.
Избегайте типизированных клиентов в одноэлементных службах
При использовании подхода "именованный клиент" внедряется в службы, а экземпляры IHttpClientFactory
создаются при вызове HttpClient
каждый раз, когда требуется CreateClient.
Однако с подходом типизированного клиента, типизированные клиенты являются преходящими объектами, которые обычно внедряются в службы. Это может вызвать проблему, так как типизированный клиент может быть внедрен в одноэлементную службу.
Внимание
Ожидается, что типизированные клиенты будут недолговечными в том же смысле, что и HttpClient
экземпляры, создаваемые IHttpClientFactory
(для получения дополнительной информации см. раздел HttpClient
"Управление временем существования"). После создания типизированного экземпляра клиента, IHttpClientFactory
теряет контроль над ним. Если типизированный экземпляр клиента сохраняется в одиночном экземпляре, это может мешать реагированию на изменения DNS, нарушив одну из целей IHttpClientFactory
.
Если необходимо использовать HttpClient
экземпляры в одиночной службе, рассмотрите следующие варианты:
- Вместо этого используйте подход с именованным клиентом, внедряя в одиночный сервис и повторно создавая
IHttpClientFactory
экземпляры при необходимости. - Если вам требуется типизированный клиент, настройте
SocketsHttpHandler
с помощьюPooledConnectionLifetime
в качестве основного обработчика. Дополнительные сведения об использованииSocketsHttpHandler
сIHttpClientFactory
см. в разделе Использование IHttpClientFactory вместе с SocketsHttpHandler.
Области обработчика сообщений в IHttpClientFactory
IHttpClientFactory
создает отдельную область DI для каждого HttpMessageHandler
экземпляра. Эти области DI отделены от областей DI приложения (например, области входящих запросов в ASP.NET или вручную созданной пользователем области DI), поэтому они не будут совместно использовать экземпляры служб с ограниченной областью действия. Области обработчика сообщений привязаны к времени существования обработчика и могут выходить за пределы областей приложений, что может привести к повторному использовании одного HttpMessageHandler
экземпляра с одинаковыми внедренными зависимостями области между несколькими входящими запросами.
Пользователям настоятельно рекомендуется не кэшировать сведения , связанные с областью действия (например, данные из HttpContext
) в HttpMessageHandler
экземплярах и использовать зависимости с областью действия с осторожностью, чтобы избежать утечки конфиденциальной информации.
Если вам требуется доступ к области DI приложения из обработчика сообщений, например, для аутентификации, вы инкапсулируете логику, поддерживающую области, в отдельный временный DelegatingHandler
, и обернёте её вокруг экземпляра HttpMessageHandler
из кэша IHttpClientFactory
. Чтобы вызвать обработчик для любого зарегистрированного IHttpMessageHandlerFactory.CreateHandler, используйте . В этом случае вы самостоятельно создадите экземпляр HttpClient
, используя обработчик, который вы создали.
В следующем примере показано создание 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;
}
Дополнительные сведения см. в полном примере.
Не полагайтесь на основного обработчика с заводскими настройками по умолчанию.
В этом разделе термин "основной обработчик по умолчанию" относится к основному обработчику, который назначается реализацией по умолчанию IHttpClientFactory
(или, более точно, реализацией HttpMessageHandlerBuilder
по умолчанию), если он никак не настроен.
Примечание.
Основной обработчик по настройкам по умолчанию завода — это деталь реализации и может измениться.
❌ ИЗБЕГАЙТЕ зависимости от конкретной реализации, которая используется как "значение по умолчанию" (например, HttpClientHandler
).
Существуют случаи, когда необходимо знать конкретный тип первичного обработчика, особенно при работе с библиотекой классов. При сохранении конфигурации конечного пользователя вам может потребоваться, например, обновить специфические свойства HttpClientHandler
, такие как ClientCertificates
, UseCookies
и UseProxy
. Может быть заманчиво преобразовать основной обработчик в HttpClientHandler
, что случайно работало, когда HttpClientHandler
использовался в качестве основного обработчика по умолчанию, установленного производителем. Но как и любой код, зависящий от деталей реализации, такой обходной путь хрупкий и склонен к сбоям.
Вместо использования первичного обработчика по умолчанию можно использовать ConfigureHttpClientDefaults
для настройки экземпляра первичного обработчика по умолчанию на уровне приложения:
// Contract with the end-user: Only HttpClientHandler is supported.
// --- "Pre-configure" stage ---
// The default is fixed as HttpClientHandler to avoid depending on the "factory-default"
// Primary Handler.
services.ConfigureHttpClientDefaults(b =>
b.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { UseCookies = false }));
// --- "End-user" stage ---
// IHttpClientBuilder builder = services.AddHttpClient("test", /* ... */);
// ...
// --- "Post-configure" stage ---
// The code can rely on the contract, and cast to HttpClientHandler only.
builder.ConfigurePrimaryHttpMessageHandler((handler, provider) =>
{
if (handler is not HttpClientHandler h)
{
throw new InvalidOperationException("Only HttpClientHandler is supported");
}
h.ClientCertificates.Add(GetClientCert(provider, builder.Name));
//X509Certificate2 GetClientCert(IServiceProvider p, string name) { ... }
});
Кроме того, можно рассмотреть возможность проверки типа основного обработчика и настройки таких параметров, как клиентские сертификаты, только в общепринятых поддерживаемых типах (скорее всего, HttpClientHandler
и SocketsHttpHandler
).
// --- "End-user" stage ---
// IHttpClientBuilder builder = services.AddHttpClient("test", /* ... */);
// ...
// --- "Post-configure" stage ---
// No contract is in place. Trying to configure main handler types supporting client
// certs, logging and skipping otherwise.
builder.ConfigurePrimaryHttpMessageHandler((handler, provider) =>
{
if (handler is HttpClientHandler h)
{
h.ClientCertificates.Add(GetClientCert(provider, builder.Name));
}
else if (handler is SocketsHttpHandler s)
{
s.SslOptions ??= new System.Net.Security.SslClientAuthenticationOptions();
s.SslOptions.ClientCertificates ??= new X509CertificateCollection();
s.SslOptions.ClientCertificates!.Add(GetClientCert(provider, builder.Name));
}
else
{
// Log warning
}
//X509Certificate2 GetClientCert(IServiceProvider p, string name) { ... }
});
См. также
-
Распространенные
IHttpClientFactory
проблемы с использованием - Внедрение зависимостей в .NET
- Логирование в .NET
- Конфигурация в .NET
- IHttpClientFactory
- IHttpMessageHandlerFactory
- HttpClient
- Выполнение HTTP-запросов с помощью HttpClient
- Реализация повторных попыток вызова HTTP с экспоненциальной задержкой