IHttpClientFactory com .NET
Neste artigo, você aprenderá a usar a interface IHttpClientFactory
para criar tipos HttpClient
com vários conceitos básicos do .NET, como a injeção de dependência (DI), registro em log e configuração. O tipo HttpClient foi introduzido no .NET Framework 4.5, que foi lançado em 2012. Em outras palavras, já existe há algum tempo. HttpClient
é usado para fazer solicitações HTTP e lidar com respostas HTTP de recursos da Web identificados por um Uri. O protocolo HTTP compõe a grande maioria de todo o tráfego da Internet.
Com os princípios de desenvolvimento de aplicativos modernos que conduzem as práticas recomendadas, o IHttpClientFactory serve como uma abstração de fábrica que pode criar instâncias HttpClient
com configurações personalizadas. IHttpClientFactory foi introduzido no .NET Core 2.1. Cargas de trabalho comuns do .NET baseadas em HTTP podem aproveitar o middleware de terceiros resiliente e transitório de manipulação de falhas com facilidade.
Observação
Se seu aplicativo exigir cookies, talvez seja melhor evitar o uso de IHttpClientFactory nele. Para obter maneiras alternativas de gerenciar clientes, consulte Diretrizes para usar clientes HTTP.
Importante
O gerenciamento de tempo de vida das instâncias HttpClient
criadas pelo 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
configurado. Para obter mais informações, consulte a seção Gerenciamento de tempo de vida do HttpClient e Diretrizes para usar clientes HTTP.
O tipo IHttpClientFactory
.
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 solicitações HTTP GET
para recuperar objetos de usuário Todo
da API de espaço reservado {JSON}.
Quando você chama qualquer um dos métodos de extensão AddHttpClient, você está adicionando IHttpClientFactory
e serviços e relacionados ao IServiceCollection. O tipo IHttpClientFactory
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 de
HttpClient
. - Codifica o conceito de middleware de saída por meio da delegação de manipuladores em
HttpClient
. - Fornece métodos de extensão para o middleware baseado em Polly para aproveitar a delegação de manipuladores em
HttpClient
. - Gerencia o cache e o tempo de vida das instâncias HttpClientHandler subjacentes. O gerenciamento automático evita problemas comuns do DNS (Sistema de Nomes de Domínio) que ocorrem ao gerenciar manualmente o tempos de vida
HttpClient
. - Adiciona uma experiência de registro em log configurável (via ILogger) para todas as solicitações enviadas por meio de clientes criados pelo alocador.
Padrões de consumo
Há várias maneiras de usar o IHttpClientFactory
em um aplicativo:
A melhor abordagem depende dos requisitos do aplicativo.
Uso básico
Para registrar IHttpClientFactory
, chame 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 construtor com DI. O código a seguir usa IHttpClientFactory
para criar uma instância 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 [];
}
}
Usar IHttpClientFactory
como no exemplo anterior é uma boa maneira de refatorar um aplicativo existente. Não há nenhum impacto na maneira em que HttpClient
é usado. Em locais em que as instâncias HttpClient
são criadas em um aplicativo existente, substitua essas ocorrências por chamadas para CreateClient.
Clientes nomeados
Clientes nomeados são uma boa opção quando:
- O aplicativo requer muitos usos distintos de
HttpClient
. - Muitas instâncias
HttpClient
têm configurações diferentes.
A configuração de um HttpClient
nomeado pode ser especificada durante o registro 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 básico
https://jsonplaceholder.typicode.com/
. - Um cabeçalho
"User-Agent"
.
Você pode usar a configuração para especificar nomes de cliente HTTP, o que é útil para evitar nomes errados 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, confira Configuração no .NET.
Criar cliente
Cada vez que CreateClient é chamado:
- Uma nova instância de
HttpClient
é criada. - A ação de configuração é chamada.
Para criar um cliente nomeado, passe o nome dele 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"];
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 [];
}
}
No código anterior, a solicitação HTTP não precisa especificar um nome do host. O código pode passar apenas o caminho, pois o endereço básico configurado para o cliente é usado.
Clientes com tipo
Clientes com tipo:
- Fornecem as mesmas funcionalidade que os clientes nomeados sem a necessidade de usar cadeias de caracteres como chaves.
- Fornecem a ajuda do IntelliSense e do compilador durante o consumo de clientes.
- Fornecem um único local para configurar e interagir com um determinado
HttpClient
. Por exemplo, um único cliente com tipo pode ser usado:- Para um único ponto de extremidade de back-end.
- Para encapsular toda a lógica que lida com o ponto de extremidade.
- Funcionam com a DI e podem ser injetados no local necessário no aplicativo.
Um cliente com tipo aceita um parâmetro HttpClient
em 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) : 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();
}
No código anterior:
- A configuração é definida quando o cliente com tipo é adicionado à coleção de serviços.
- A opção
HttpClient
é atribuída como uma variável de escopo de classe (campo) e usada com APIs expostas.
Métodos específicos da API podem ser criados que expõem a funcionalidade HttpClient
. Por exemplo, o método GetUserTodosAsync
encapsula o código para recuperar objetos Todo
específicos do usuário.
O código a seguir chamaAddHttpClient para registrar uma classe de cliente com tipo:
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 com tipo é registrado como transitório com a DI. No código anterior, AddHttpClient
registra TodoService
como um serviço transitório. Esse registro usa 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 seu construtor.
Importante
O uso de clientes tipados em serviços singleton pode ser perigoso. Para obter mais informações, consulte a seção Evitar clientes tipados em serviços singleton.
Observação
Ao registrar um cliente tipado com o método AddHttpClient<TClient>
, o tipo TClient
deve ter um construtor que aceite HttpClient
como um parâmetro. Além disso, o tipo TClient
não deve ser registrado com o contêiner de DI separadamente, pois isso fará com que o registro posterior substitua o anterior.
Clientes gerados
IHttpClientFactory
pode ser usado em combinação com bibliotecas de terceiros, como a Refit. Refit é uma biblioteca REST para .NET. Ele permite definições declarativas de API REST, mapeando métodos de interface para pontos de extremidade. Uma implementação da interface é gerada dinamicamente pelo RestService
usando HttpClient
para fazer as chamadas de HTTP externas.
Considere o seguinte tipo record
:
namespace Shared;
public record class Todo(
int UserId,
int Id,
string Title,
bool Completed);
O exemplo a seguir depende do pacote NuGet Refit.HttpClientFactory
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 uma instânciaTask<Todo[]>
. - Declara um atributo
Refit.GetAttribute
com o caminho e a cadeia de caracteres de consulta para a API externa.
Um cliente com tipo pode ser adicionado usando a 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 pela Refit.
Fazer solicitações POST, PUT e DELETE
Nos exemplos anteriores, todas as solicitações HTTP usam o verbo HTTP GET
. HttpClient
também dá suporte a outros verbos HTTP, incluindo:
POST
PUT
DELETE
PATCH
Para obter uma lista completa de verbos HTTP compatíveis, consulte HttpMethod. Para obter mais informações sobre como fazer solicitações HTTP, confira Enviar uma solicitação usando HttpClient.
O exemplo a seguir mostra como fazer uma solicitação POST
HTTP:
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 parâmetro
Item
para JSON usandoSystem.Text.Json
. Ele 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 o corpo da solicitação HTTP.
- Chama PostAsync para enviar o conteúdo JSON para a URL especificada. Essa é uma URL relativa que é adicionada ao HttpClient.BaseAddress.
- Chama EnsureSuccessStatusCode para gerar uma exceção se o código de status de resposta não indicar êxito.
HttpClient
também dá suporte a outros tipos de conteúdo. Por exemplo, MultipartContent e StreamContent. Para obter uma lista completa dos dispositivos com suporte, consulte HttpContent.
O exemplo a seguir mostra uma solicitação PUT
HTTP:
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 exemplo POST
. O método UpdateItemAsync
chama PutAsync em vez de PostAsync
.
O exemplo a seguir mostra uma solicitação DELETE
HTTP:
public async Task DeleteItemAsync(Guid id)
{
using HttpResponseMessage httpResponse =
await httpClient.DeleteAsync($"/api/items/{id}");
httpResponse.EnsureSuccessStatusCode();
}
No código anterior, o método DeleteItemAsync
chama DeleteAsync. Como as solicitações HTTP DELETE normalmente não contêm corpo, o método DeleteAsync
não fornece uma sobrecarga que aceita uma instância de HttpContent
.
Para saber mais sobre como usar verbos HTTP diferentes com HttpClient
, consulte HttpClient.
HttpClient
gerenciamento do tempo de vida
Uma nova instância de HttpClient
é chamada, sempre que CreateClient
é chamado na IHttpClientFactory
. Uma instância HttpClientHandler é criada por nome do cliente. O alocador gerencia a vida útil das instâncias do HttpClientHandler
.
IHttpClientFactory
armazena em cache as instâncias de HttpClientHandler
criadas pelo alocador para reduzir o consumo de recursos. Uma instância de HttpClientHandler
poderá ser reutilizada do cache, ao criar uma nova instância de HttpClient
, se o respectivo tempo de vida não tiver expirado.
O cache de manipuladores é preferível porque normalmente cada manipulador gerencia o próprio pool de conexão HTTP subjacente. Criar mais manipuladores do que o necessário pode resultar no esgotamento do soquete e em atrasos de conexão. Alguns manipuladores também mantêm as conexões abertas indefinidamente, o que pode impedir que o manipulador reaja a alterações de DNS.
O tempo de vida padrão do manipulador é de 2 minutos. Para substituir o valor padrão, chame SetHandlerLifetime para cada cliente, no IServiceCollection
:
services.AddHttpClient("Named.Client")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Importante
As instâncias HttpClient
criadas por IHttpClientFactory
devem ser de curta duração.
Reciclar e recriar
HttpMessageHandler
s quando o tempo de vida deles expira é essencial paraIHttpClientFactory
garantir que os manipuladores reajam às alterações de DNS.HttpClient
é vinculado a uma instância de manipulador específica no momento de sua criação, portanto, novas instânciasHttpClient
devem ser solicitadas em tempo hábil para garantir que o cliente receberá o manipulador atualizado.O descarte dessas instâncias
HttpClient
criadas pelo alocador não leva ao esgotamento do soquete, pois seu descarte não dispara o descarte doHttpMessageHandler
.IHttpClientFactory
rastreia e descarta os recursos usados para criar instânciasHttpClient
, especificamente as instânciasHttpMessageHandler
, assim que o tempo de vida delas expira eHttpClient
não as utiliza mais.
Manter uma só instância HttpClient
ativa por uma longa duração é um padrão comum que pode ser usado como uma alternativa a IHttpClientFactory
, no entanto, esse padrão requer configuração adicional, como PooledConnectionLifetime
. Você pode usar clientes de longa duração com PooledConnectionLifetime
ou clientes de curta duração criados pelo IHttpClientFactory
. Para obter informações sobre qual estratégia usar em seu aplicativo, consulte Diretrizes para usar clientes HTTP.
Configurar o HttpMessageHandler
Talvez seja necessário controlar a configuração do HttpMessageHandler interno usado por um cliente.
Um IHttpClientBuilder é retornado ao adicionar clientes nomeados ou com tipo. O método de extensão ConfigurePrimaryHttpMessageHandler pode ser usado para definir um representante no IServiceCollection
. O representante que é usado para criar e configurar o HttpMessageHandler
primário usado pelo cliente:
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
Configurar o HttClientHandler
permite que você especifique um proxy para a instância de HttpClient
entre várias outras propriedades do manipulador. Para obter mais informações, consulte Proxy por cliente.
Configuração adicional
Há 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 chamado HttpClient . |
AddTypedClient | Configura a associação entre o TClient e o HttpClient nomeado associado a IHttpClientBuilder . |
ConfigureHttpClient | Adiciona um delegado que será usado para configurar um HttpClient nomeado. |
ConfigurePrimaryHttpMessageHandler | Configura o HttpMessageHandler primário do contêiner de injeção de dependência para um HttpClient nomeado. |
RedactLoggedHeaders | Define a coleção de nomes de cabeçalho HTTP para os quais os valores devem ser reeditados antes do registro em log. |
SetHandlerLifetime | Define o período em que uma instância de HttpMessageHandler pode ser reutilizada. Cada cliente nomeado pode ter o próprio valor de tempo de vida do manipulador configurado. |
UseSocketsHttpHandler | Configura uma instância SocketsHttpHandler nova ou adicionada anteriormente do contêiner de injeção de dependência a ser usada como um manipulador primário para um HttpClient nomeado. (apenas .NET 5+) |
Usando IHttpClientFactory com SocketsHttpHandler
A implementação SocketsHttpHandler
de HttpMessageHandler
foi adicionada ao .NET Core 2.1, que permite a configuração de PooledConnectionLifetime
. Essa 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, confira Diretrizes para usar clientes HTTP.
No entanto, SocketsHttpHandler
e IHttpClientFactory
podem ser usados juntos para melhorar a configurabilidade. Usando essas duas APIs, você se beneficia da configurabilidade em nível baixo (por exemplo, usar LocalCertificateSelectionCallback
para seleção de certificado dinâmico) e em nível alto (por exemplo, aproveitar a integração de DI e várias configurações de cliente).
Para usar as duas APIs:
- Especifique
SocketsHttpHandler
comoPrimaryHandler
usando ConfigurePrimaryHttpMessageHandler ou UseSocketsHttpHandler (somente .NET 5+). - Configure SocketsHttpHandler.PooledConnectionLifetime com base no intervalo em que você espera que o DNS seja atualizado; por exemplo, para um valor que estava anteriormente em
HandlerLifetime
. - (Opcional) Como
SocketsHttpHandler
lidará com o pool de conexões e com a reciclagem, a reciclagem do manipulador no nívelIHttpClientFactory
não será mais necessária. Você pode desabilitá-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 ilustrativos, 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 mais informações, consulte a seção Comportamento do DNS nas HttpClient
diretrizes e a seção Comentários na documentação da API PooledConnectionLifetime.
Evitar clientes tipados em serviços singleton
Ao usar a abordagem de cliente nomeado, IHttpClientFactory
é injetado em serviços e instâncias HttpClient
são criadas chamando CreateClient sempre que um HttpClient
é necessário.
No entanto, com a abordagem de cliente tipado, os clientes tipados são objetos transitórios geralmente injetados em serviços. Isso pode causar um problema porque um cliente tipado pode ser injetado em um serviço singleton.
Importante
Espera-se que os clientes tipados sejam de curta duração no mesmo sentido das instâncias HttpClient
criadas por IHttpClientFactory
(para obter mais informações, consulte gerenciamento de tempo de vida do HttpClient
). Assim que uma instância de cliente tipado é criada, IHttpClientFactory
não tem controle sobre ela. Se uma instância de cliente tipado for capturada em um singleton, ela poderá impedir que ele reaja às alterações de DNS, eliminando uma das finalidades de IHttpClientFactory
.
Se você precisar usar instâncias HttpClient
em um serviço singleton, considere as seguintes opções:
- Use a abordagem de cliente nomeado, injetando
IHttpClientFactory
no serviço singleton e recriando instânciasHttpClient
quando necessário. - Se você precisar da abordagem de cliente tipado, use
SocketsHttpHandler
comPooledConnectionLifetime
configurado como um manipulador primário. Para obter mais informações sobre como usarSocketsHttpHandler
comIHttpClientFactory
, consulte a seção Usando IHttpClientFactory com SocketsHttpHandler.
Escopos do manipulador de mensagens em IHttpClientFactory
IHttpClientFactory
cria um escopo de DI separado para cada instância HttpMessageHandler
. Esses escopos de DI são separados dos escopos de DI do aplicativo (por exemplo, escopo de solicitação de entrada de ASP.NET ou escopo de DI manual criado pelo usuário), portanto, eles não compartilham instâncias de serviço com escopo. Os escopos do Manipulador de Mensagens estão vinculados ao tempo de vida do manipulador e podem durar mais que os escopos do aplicativo, o que pode levar, por exemplo, a reutilização da mesma instância HttpMessageHandler
com as mesmas dependências com escopo injetadas entre várias solicitações de entrada.
Os usuários são altamente aconselhados a não armazenar em cache informações relacionadas ao escopo (como dados de HttpContext
) dentro de instâncias HttpMessageHandler
e usar dependências com escopo com cuidado para evitar o vazamento de informações confidenciais.
Se precisasse de acesso a um escopo de DI de aplicativo do manipulador de mensagens, para autenticação por exemplo, você encapsularia a lógica com reconhecimento de escopo em um DelegatingHandler
transitório separado, e a encapsularia em torno de uma instância de HttpMessageHandler
do cache IHttpClientFactory
. Para acessar o manipulador, chame IHttpMessageHandlerFactory.CreateHandler para qualquer cliente nomeado registrado. Nesse caso, você mesmo criaria uma instância de HttpClient
usando o manipulador construído.
O exemplo a seguir mostra a criação de um HttpClient
com um DelegatingHandler
com reconhecimento de 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);
Outra solução alternativa pode seguir com um método de extensão para registrar um DelegatingHandler
com reconhecimento de escopo e substituir o registro IHttpClientFactory
padrão por um serviço transitório com acesso ao escopo atual do aplicativo:
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 saber mais, consulte o exemplo completo.