Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Neste artigo, você aprenderá a usar a IHttpClientFactory
interface para criar HttpClient
tipos com vários fundamentos do .NET, como DI (injeção de dependência), registro em log e configuração. O HttpClient tipo foi introduzido no .NET Framework 4.5, que foi lançado em 2012. Ou seja, já existe há algum tempo.
HttpClient
é usado para fazer solicitações HTTP e manipular respostas HTTP de recursos web identificados por um Uri. O protocolo HTTP constitui a grande maioria de todo o tráfego da Internet.
Com princípios modernos de desenvolvimento de aplicativos orientando as práticas recomendadas, o IHttpClientFactory serve como uma abstração de fábrica que pode criar HttpClient
instâncias com configurações personalizadas.
IHttpClientFactory foi introduzido no .NET Core 2.1. Trabalhos .NET comuns baseados em HTTP podem com facilidade tirar partido de middleware de terceiros resiliente e de gestão de falhas transitórias.
Advertência
Se a sua aplicação requer cookies, é recomendável evitar o uso de IHttpClientFactory. O agrupamento das HttpMessageHandler instâncias resulta no compartilhamento de CookieContainer objetos. O compartilhamento inesperado CookieContainer pode causar vazamento de cookies entre partes não relacionadas da aplicação. Além disso, quando HandlerLifetime expira, o gestor é reciclado, o que significa que todos os cookies armazenados no seu CookieContainer são perdidos. Para obter formas alternativas de gerenciar clientes, consulte Diretrizes para usar clientes HTTP.
Importante
O gerenciamento do tempo de vida das HttpClient
instâncias criadas por IHttpClientFactory
é completamente diferente das instâncias criadas manualmente. As estratégias são usar clientes de curta duração criados por IHttpClientFactory
ou clientes de longa duração com PooledConnectionLifetime
configurados. Para obter mais informações, consulte a seção Gerenciamento do tempo de vida do HttpClient e Diretrizes para o uso de clientes HTTP.
O IHttpClientFactory
tipo
Todo o código-fonte de exemplo fornecido neste artigo requer a instalação do Microsoft.Extensions.Http
pacote NuGet. Além disso, os exemplos de código demonstram o uso de pedidos HTTP GET
para recuperar objetos de utilizador Todo
da API {JSON} Placeholder gratuita.
Ao chamar qualquer um dos métodos de extensão de AddHttpClient, está-se a adicionar IHttpClientFactory
e os serviços relacionados ao IServiceCollection. O IHttpClientFactory
tipo oferece os seguintes benefícios:
- Expõe a classe
HttpClient
como um tipo pronto para DI. - Fornece um local central para nomear e configurar instâncias lógicas
HttpClient
. - Codifica o conceito de middleware de saída através da delegação de manipuladores em
HttpClient
. - Fornece métodos de extensão para middleware baseado em Polly para tirar proveito da delegação de manipuladores no
HttpClient
. - Gerencia o cache e o tempo de vida das instâncias subjacentes HttpClientHandler . A gestão automática evita problemas comuns do Sistema de Nomes de Domínio (DNS) que ocorrem ao gerir manualmente a duração de vida de
HttpClient
. - Adiciona uma experiência de registro configurável (via ILogger) para todas as solicitações enviadas através de clientes criados pela fábrica.
Padrões de consumo
Existem várias maneiras como IHttpClientFactory
pode ser usado numa aplicação.
A melhor abordagem depende dos requisitos do aplicativo.
Utilização básica
Para registar o IHttpClientFactory
, ligue para 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();
O consumo de serviços pode exigir o IHttpClientFactory
como parâmetro de construtor com DI. O código a seguir usa IHttpClientFactory
para criar uma HttpClient
instância:
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 [];
}
}
Usar IHttpClientFactory
como no exemplo anterior é uma boa maneira de refatorar um aplicativo existente. Não tem impacto na forma como HttpClient
é utilizado. Em locais onde instâncias de HttpClient
são criadas numa aplicação existente, substitua essas ocorrências por chamadas a CreateClient.
Clientes nomeados
Clientes nomeados são uma boa escolha quando:
- O aplicativo requer muitos usos distintos do
HttpClient
. - Muitas
HttpClient
instâncias têm configurações diferentes.
A configuração para um HttpClient
nomeado pode ser especificada durante o registo no 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");
});
No código anterior, o cliente é configurado com:
- Um nome que é extraído da configuração sob o
"TodoHttpClientName"
. - O endereço
https://jsonplaceholder.typicode.com/
. - Um
"User-Agent"
cabeçalho.
Você pode usar a configuração para especificar nomes de clientes HTTP, o que é útil para evitar nomes incorretos de clientes ao adicionar e criar. Neste exemplo, o arquivo appsettings.json é usado para configurar o nome do cliente HTTP:
{
"TodoHttpClientName": "JsonPlaceholderApi"
}
É fácil estender essa configuração e armazenar mais detalhes sobre como você gostaria que seu cliente HTTP funcionasse. Para obter mais informações, consulte Configuração no .NET.
Criar cliente
Cada vez que CreateClient é chamado:
- Uma nova instância do
HttpClient
é criada. - Chama-se a ação de configuração.
Para criar um cliente nomeado, passe seu nome para 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 [];
}
}
No código anterior, a solicitação HTTP não precisa especificar um nome de host. O código pode passar apenas o caminho, uma vez que o endereço base configurado para o cliente é usado.
Clientes tipificados
Clientes tipificados
- Forneça os mesmos recursos que os clientes nomeados sem a necessidade de usar cadeias de caracteres como chaves.
- Ofereça IntelliSense e ajuda do compilador quando os clientes estiverem a consumir os serviços.
- Forneça um único local para configurar e interagir com um determinado
HttpClient
. Por exemplo, um único cliente tipificado pode ser usado:- Para um único endpoint de backend.
- Para encapsular toda a lógica que lida com o endpoint.
- Trabalhe com DI e pode ser injetado quando necessário no aplicativo.
Um cliente tipado aceita um HttpClient
parâmetro no seu construtor:
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 [];
}
}
No código anterior:
- A configuração é definida quando o cliente digitado é adicionado à coleção de serviços.
- O
HttpClient
é atribuído como uma variável de escopo de classe (campo) e usado com APIs expostas.
Métodos específicos de API podem ser criados para expor HttpClient
funcionalidade. Por exemplo, o GetUserTodosAsync
método encapsula código para recuperar objetos específicos Todo
do usuário.
O código a seguir chama AddHttpClient para registrar uma classe de cliente digitada:
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");
});
O cliente digitado é registrado como transitório com DI. No código anterior, AddHttpClient
registra-se TodoService
como um serviço transitório. Este registo utiliza um método de fábrica para:
- Crie uma instância de
HttpClient
. - Crie uma instância de
TodoService
, passando a instância deHttpClient
para o seu construtor.
Importante
Usar clientes digitados em serviços singleton pode ser perigoso. Para obter mais informações, consulte a seção Evitar clientes tipificados em serviços singleton.
Nota
Ao registrar um cliente tipado com o AddHttpClient<TClient>
método, o TClient
tipo deve ter um construtor que aceite um HttpClient
como parâmetro. Além disso, o tipo TClient
não deve ser registado separadamente no contêiner DI, pois isso fará com que o registo posterior substitua o anterior.
Clientes gerados
IHttpClientFactory
pode ser usado em combinação com bibliotecas de terceiros, como o Refit. Refit é uma biblioteca REST para .NET. Permite definições declarativas de API REST, associando métodos de interface a pontos de extremidade. Uma implementação da interface é gerada dinamicamente pelo RestService
, usando HttpClient
para fazer as chamadas HTTP externas.
Considere o seguinte record
tipo:
namespace Shared;
public record class Todo(
int UserId,
int Id,
string Title,
bool Completed);
O exemplo a seguir depende do Refit.HttpClientFactory
pacote NuGet e é uma interface simples:
using Refit;
using Shared;
namespace GeneratedHttp.Example;
public interface ITodoService
{
[Get("/todos?userId={userId}")]
Task<Todo[]> GetUserTodosAsync(int userId);
}
A interface C# anterior:
- Define um método chamado
GetUserTodosAsync
que retorna umaTask<Todo[]>
instância. - Declara um
Refit.GetAttribute
atributo com o caminho e a cadeia de caracteres de consulta para a API externa.
Um cliente tipado pode ser adicionado, usando o Refit para gerar a implementação:
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");
});
A interface definida pode ser consumida quando necessário, com a implementação fornecida pela DI e Refit.
Fazer solicitações POST, PUT e DELETE
Nos exemplos anteriores, todas as solicitações HTTP usam o verbo GET
HTTP.
HttpClient
também suporta outros verbos HTTP, incluindo:
POST
PUT
DELETE
PATCH
Para obter uma lista completa dos verbos HTTP suportados, consulte HttpMethod. Para obter mais informações sobre como fazer solicitações HTTP, consulte Enviar uma solicitação usando HttpClient.
O exemplo a seguir mostra como fazer uma solicitação 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();
}
No código anterior, o método CreateItemAsync
:
- Serializa o
Item
parâmetro para JSON usandoSystem.Text.Json
. Isto usa uma instância de JsonSerializerOptions para configurar o processo de serialização. - Cria uma instância de StringContent para empacotar o JSON serializado para enviar no corpo da solicitação HTTP.
- Chamadas PostAsync para enviar o conteúdo JSON para a URL especificada. Esta é uma URL relativa que é adicionada ao HttpClient.BaseAddress.
- Chama EnsureSuccessStatusCode para lançar uma exceção se o código de status da resposta não indicar sucesso.
HttpClient
também suporta outros tipos de conteúdo. Por exemplo, MultipartContent e StreamContent. Para obter uma lista completa do conteúdo suportado, consulte HttpContent.
O exemplo a seguir mostra uma solicitação 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();
}
O código anterior é muito semelhante ao POST
exemplo. O UpdateItemAsync
método chama PutAsync em vez de PostAsync
.
O exemplo a seguir mostra uma solicitação HTTP DELETE
:
public async Task DeleteItemAsync(Guid id)
{
using HttpResponseMessage httpResponse =
await httpClient.DeleteAsync($"/api/items/{id}");
httpResponse.EnsureSuccessStatusCode();
}
No código anterior, o DeleteItemAsync
método chama DeleteAsync. Como as solicitações HTTP DELETE normalmente não contêm corpo, o DeleteAsync
método não fornece uma sobrecarga que aceita uma instância de HttpContent
.
Para obter mais informações sobre a utilização de diferentes verbos HTTP com HttpClient
, consulte HttpClient.
HttpClient
gestão do tempo de vida
Uma nova HttpClient
instância é retornada sempre que CreateClient
é chamada no IHttpClientFactory
. Uma HttpClientHandler instância é criada por nome de cliente. A fábrica gerencia o tempo de vida das HttpClientHandler
instâncias.
IHttpClientFactory
Armazena em cache as HttpClientHandler
instâncias criadas pela fábrica para reduzir o consumo de recursos. Uma HttpClientHandler
instância pode ser reutilizada do cache ao criar uma nova HttpClient
instância se seu tempo de vida não tiver expirado.
O armazenamento em cache de manipuladores é desejável, pois cada manipulador normalmente gerencia seu próprio pool de conexões HTTP subjacente. Criar mais manipuladores do que o necessário pode resultar em exaustão do soquete e atrasos de conexão. Alguns manipuladores também mantêm as conexões abertas indefinidamente, o que pode impedir que o manipulador reaja às alterações de DNS.
O tempo de vida padrão do manipulador é de dois minutos. Para substituir o valor padrão, chame SetHandlerLifetime para cada cliente, no IServiceCollection
:
services.AddHttpClient("Named.Client")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Importante
HttpClient
As instâncias criadas por IHttpClientFactory
destinam-se a ser de curta duração.
Reciclar e recriar
HttpMessageHandler
's quando sua vida útil expira é essencial paraIHttpClientFactory
garantir que os manipuladores reajam às alterações de DNS.HttpClient
está vinculado a uma instância de manipulador específica após sua criação, portanto, novasHttpClient
instâncias devem ser solicitadas em tempo hábil para garantir que o cliente obtenha o manipulador atualizado.A eliminação dessas instâncias
HttpClient
criadas pela fábrica não levará ao esgotamento dos sockets, pois seu descarte não desencadeará o descarte doHttpMessageHandler
.IHttpClientFactory
rastreia e elimina os recursos usados para criarHttpClient
instâncias, especificamente asHttpMessageHandler
instâncias, assim que a sua vida útil expira e já não há mais nenhumHttpClient
a usá-los.
Manter uma única HttpClient
instância ativa por um longo período é um padrão comum que pode ser usado como uma alternativa ao IHttpClientFactory
, no entanto, esse padrão requer configuração adicional, como PooledConnectionLifetime
. Pode utilizar tanto clientes de longa duração com PooledConnectionLifetime
, como clientes de curta duração criados por IHttpClientFactory
. Para obter informações sobre qual estratégia usar em seu aplicativo, consulte Diretrizes para usar clientes HTTP.
Configura o HttpMessageHandler
Pode ser necessário controlar a configuração interna do HttpMessageHandler utilizado por um cliente.
Um IHttpClientBuilder é retornado ao adicionar clientes nomeados ou digitados. O método de extensão ConfigurePrimaryHttpMessageHandler pode ser usado para definir um delegado em IServiceCollection
. O delegado é usado para criar e configurar o componente principal HttpMessageHandler
usado por aquele cliente.
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
Configurar o HttClientHandler
permite especificar um proxy para a HttpClient
instância entre várias outras propriedades do manipulador. Para obter mais informações, consulte Proxy por cliente.
Configuração adicional
Existem várias opções de configuração adicionais para controlar o IHttpClientHandler
:
Método | Descrição |
---|---|
AddHttpMessageHandler | Adiciona um manipulador de mensagens adicional para um HttpClient nomeado. |
AddTypedClient | Configura a vinculação entre o TClient e o HttpClient designado associado ao IHttpClientBuilder . |
ConfigureHttpClient | Adiciona um delegado que será usado para configurar uma configuração nomeada HttpClient . |
ConfigurePrimaryHttpMessageHandler | Configura o componente principal HttpMessageHandler do contentor de injeção de dependência para um componente nomeado HttpClient . |
RedactLoggedHeaders | Define a coleção de nomes de cabeçalho HTTP para os quais os valores devem ser editados antes do registro. |
SetHandlerLifetime | Define o período de tempo durante o qual uma HttpMessageHandler instância pode ser reutilizada. Cada cliente nomeado pode ter seu próprio valor de vida útil do manipulador configurado. |
UseSocketsHttpHandler | Configura uma nova instância ou uma instância adicionada SocketsHttpHandler anteriormente do contentor de injeção de dependência para ser usada como um manipulador primário para um HttpClient nomeado. (Somente .NET 5+) |
Usando IHttpClientFactory junto com SocketsHttpHandler
A implementação de SocketsHttpHandler
foi adicionada no HttpMessageHandler
.NET Core 2.1, que permite que PooledConnectionLifetime
seja configurado. Esta configuração é usada para garantir que o manipulador reaja às alterações de DNS; portanto, usar SocketsHttpHandler
é considerado uma alternativa ao uso de IHttpClientFactory
. Para obter mais informações, consulte Diretrizes para usar clientes HTTP.
No entanto, SocketsHttpHandler
e IHttpClientFactory
pode ser usado em conjunto melhorar a configurabilidade. Ao usar ambas as APIs, você se beneficia da configurabilidade em um nível baixo (por exemplo, usando LocalCertificateSelectionCallback
para seleção dinâmica de certificados) e alto nível (por exemplo, aproveitando a integração DI e várias configurações de cliente).
Para usar ambas as APIs:
- Especifique
SocketsHttpHandler
comoPrimaryHandler
via ConfigurePrimaryHttpMessageHandler, ou UseSocketsHttpHandler (somente .NET 5+). -
SocketsHttpHandler.PooledConnectionLifetime Configurar com base no intervalo que o utilizador espera que o DNS seja atualizado; por exemplo, para um valor que estava anteriormente em
HandlerLifetime
. - (Opcional) Uma vez que
SocketsHttpHandler
irá lidar com o pool de ligações e a reciclagem, a reciclagem do manipuladorIHttpClientFactory
ao nível já não é necessária. Você pode desativá-lo definindoHandlerLifetime
comoTimeout.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
No exemplo acima, 2 minutos foram escolhidos arbitrariamente para fins de ilustração, alinhando-se a um valor padrão HandlerLifetime
. Você deve escolher o valor com base na frequência esperada de DNS ou outras alterações de rede. Para obter mais informações, consulte a seção Comportamento do HttpClient
DNS nas diretrizes e a seção Comentários na documentação da PooledConnectionLifetime API.
Evite clientes tipificados em serviços singleton
Ao usar a abordagem de cliente nomeado, é injetado IHttpClientFactory
No entanto, com a abordagem de cliente tipado, os clientes tipados são objetos transitórios habitualmente injetados em serviços. Isso pode causar um problema porque um cliente digitado pode ser injetado em um serviço singleton.
Importante
Espera-se que os clientes tipados sejam de curta duração no mesmo sentido HttpClient
das instâncias criadas por IHttpClientFactory
(para obter mais informações, consulte HttpClient
Gerenciamento de tempo de vida). Assim que uma instância de cliente tipada é criada, IHttpClientFactory
não tem controle sobre ela. Se uma instância de cliente tipada for capturada em um singleton, ela poderá impedir que reaja às alterações de DNS, comprometendo uma das finalidades do IHttpClientFactory
.
Caso precise utilizar instâncias de HttpClient
num serviço singleton, considere as seguintes opções:
- Em vez disso, use a abordagem de cliente nomeado, injetando no serviço singleton
IHttpClientFactory
e recriando instânciasHttpClient
quando necessário. - Se precisar da abordagem de cliente tipificada, use
SocketsHttpHandler
comPooledConnectionLifetime
configurado como manipulador primário. Para obter mais informações sobre como usarSocketsHttpHandler
comIHttpClientFactory
, consulte a seção Usando IHttpClientFactory junto com SocketsHttpHandler.
Escopos do manipulador de mensagens em IHttpClientFactory
IHttpClientFactory
cria um escopo de DI separado para cada HttpMessageHandler
instância. Esses escopos DI são separados dos escopos DI do aplicativo (por exemplo, ASP.NET escopo de solicitação de entrada ou um escopo DI manual criado pelo usuário), portanto, eles não compartilharão instâncias de serviço com escopo. Os escopos do manipulador de mensagens estão vinculados ao tempo de vida do manipulador e podem sobreviver aos escopos do aplicativo, o que pode levar, por exemplo, à reutilização da mesma HttpMessageHandler
instância com as mesmas dependências de escopo injetadas entre várias solicitações de entrada.
Os usuários são fortemente aconselhados a não armazenar em cache informações relacionadas ao escopo (como dados de HttpContext
) dentro de instâncias HttpMessageHandler
e a usar dependências com escopo com cuidado para evitar o vazamento de informações confidenciais.
Se tiver necessidade de aceder a um escopo de DI de aplicativo a partir do seu próprio manipulador de mensagens, por exemplo, para autenticação, deverá encapsular a lógica com reconhecimento de escopo num transitório DelegatingHandler
separado e encapsulá-la em torno de uma instância HttpMessageHandler
do cache IHttpClientFactory
. Para aceder à chamada do manipulador IHttpMessageHandlerFactory.CreateHandler para qualquer cliente nomeado registrado. Nesse caso, você mesmo criaria uma HttpClient
instância usando o manipulador construído.
O exemplo a seguir mostra a criação de um HttpClient
com um DelegatingHandler
ciente do escopo:
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);
Uma abordagem alternativa adicional pode seguir com um método de extensão para registar um DelegatingHandler
com reconhecimento de escopo e substituir o registo padrão de IHttpClientFactory
por um serviço transitório com acesso ao escopo atual da aplicação.
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;
}
Para obter mais informações, consulte o exemplo completo.
Evite depender de um gestor primário "padrão de fábrica"
Nesta seção, o termo "padrão de fábrica" Manipulador Primário refere-se ao Manipulador Primário que a implementação padrão IHttpClientFactory
(ou, mais precisamente, a implementação padrão HttpMessageHandlerBuilder
) atribui se não configurada de forma alguma.
Nota
O manipulador primário "padrão de fábrica" é um detalhe de implementação e está sujeito a alterações.
❌ EVITE depender de uma implementação específica que está sendo usada como um "padrão de fábrica" (por exemplo, HttpClientHandler
).
Há casos em que precisa saber o tipo específico de um Manipulador Primário, especialmente se estiver a trabalhar numa biblioteca de classes. Ao mesmo tempo que preserva a configuração do utilizador final, pode querer atualizar, por exemplo, propriedades específicas de HttpClientHandler
, como ClientCertificates
, UseCookies
e UseProxy
. Pode ser tentador converter o Manipulador Primário em HttpClientHandler
, o que funcionou enquantoHttpClientHandler
era usado como o Manipulador Primário "padrão de fábrica". Mas, como qualquer código dependendo dos detalhes da implementação, essa solução alternativa é frágil e fadada a quebrar.
Em vez de confiar no Manipulador Primário "padrão de fábrica", você pode usáConfigureHttpClientDefaults
para configurar uma instância do Manipulador Primário padrão no nível do aplicativo:
// 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) { ... }
});
Como alternativa, você pode considerar verificar o tipo de manipulador primário e configurar as especificidades, como certificados de cliente, somente nos tipos de suporte conhecidos (provavelmente, HttpClientHandler
e 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) { ... }
});