IHttpClientFactory com .NET
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 da Web identificados por um Uriarquivo . 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. Cargas de trabalho .NET comuns baseadas em HTTP podem tirar proveito do middleware de terceiros resiliente e de tratamento de falhas transitórias com facilidade.
Nota
Se o seu aplicativo requer cookies, talvez seja melhor evitar o uso IHttpClientFactory no seu aplicativo. 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 vida com PooledConnectionLifetime
configuração. 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 solicitações HTTP GET
para recuperar objetos de usuário Todo
da API de espaço reservado {JSON} livre.
Ao chamar qualquer um dos métodos de AddHttpClient extensão, você está adicionando os IHttpClientFactory
serviços relacionados ao IServiceCollection. O IHttpClientFactory
tipo oferece os seguintes benefícios:
- Expõe a
HttpClient
classe como um tipo DI-ready. - 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 . O gerenciamento automático evita problemas comuns de DNS (Sistema de Nomes de Domínio) que ocorrem ao gerenciar manualmente os tempos de
HttpClient
vida. - 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
Há várias maneiras de IHttpClientFactory
usar em um aplicativo:
A melhor abordagem depende dos requisitos do aplicativo.
Utilização básica
Para registar o IHttpClientFactory
, ligue AddHttpClient
para:
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
parâmetro as a constructor 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
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 tem impacto na forma como HttpClient
é utilizado. Em locais onde HttpClient
as instâncias são criadas em um aplicativo existente, substitua essas ocorrências por chamadas para 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 de um nome HttpClient
pode ser especificada durante o IServiceCollection
registro no :
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 é retirado da configuração sob o
"TodoHttpClientName"
. - O endereço
https://jsonplaceholder.typicode.com/
base . - 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 CreateClient é chamado:
- Uma nova instância do
HttpClient
é criada. - A ação de configuração é chamada.
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"];
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 de host. O código pode passar apenas o caminho, uma vez que o endereço base configurado para o cliente é usado.
Clientes digitados
Clientes digitados:
- Forneça os mesmos recursos que os clientes nomeados sem a necessidade de usar cadeias de caracteres como chaves.
- Forneça ajuda do IntelliSense e do compilador ao consumir clientes.
- Forneça um único local para configurar e interagir com um determinado
HttpClient
. Por exemplo, um único cliente tipado 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.
- Trabalhe com DI e pode ser injetado quando necessário no aplicativo.
Um cliente tipado aceita um HttpClient
parâmetro 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 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
a 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 seu construtor.
Importante
Usar clientes digitados em serviços singleton pode ser perigoso. Para obter mais informações, consulte a seção Evitar clientes digitados 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 TClient
tipo não deve ser registrado com o contêiner DI separadamente, pois isso levará ao registro posterior substituindo o primeiro.
Clientes gerados
IHttpClientFactory
pode ser usado em combinação com bibliotecas de terceiros, como o 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 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 CreateItemAsync
método:
- Serializa o
Item
parâmetro para JSON usandoSystem.Text.Json
. Isso usa uma instância de para configurar o processo de JsonSerializerOptions serialização. - Cria uma instância de para empacotar o JSON serializado StringContent 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.
- Chamadas EnsureSuccessStatusCode para lançar uma exceção se o código de status da resposta não indicar êxito.
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 saber mais sobre como usar diferentes verbos HTTP com HttpClient
o , 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 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.O descarte de tais
HttpClient
instâncias criadas pela fábrica não levará ao esgotamento da tomada, pois seu descarte não desencadeará oHttpMessageHandler
descarte do .IHttpClientFactory
rastreia e elimina os recursos usados para criarHttpClient
instâncias, especificamente asHttpMessageHandler
instâncias, assim que sua vida útil expira e nãoHttpClient
há mais uso.
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
. Você pode usar clientes de longa duração com PooledConnectionLifetime
, ou clientes de curta duração criados pela IHttpClientFactory
. Para obter informações sobre qual estratégia usar em seu aplicativo, consulte Diretrizes para usar clientes HTTP.
Configure o HttpMessageHandler
Pode ser necessário controlar a configuração do interior HttpMessageHandler utilizado por um cliente.
Um IHttpClientBuilder é retornado ao adicionar clientes nomeados ou digitados. O ConfigurePrimaryHttpMessageHandler método extension pode ser usado para definir um delegado no IServiceCollection
. O delegado é usado para criar e configurar o primário HttpMessageHandler
usado por esse 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 | Description |
---|---|
AddHttpMessageHandler | Adiciona um manipulador de mensagens adicional para um arquivo HttpClient . |
AddTypedClient | Configura a associação entre o TClient e o nomeado HttpClient associado ao IHttpClientBuilder . |
ConfigureHttpClient | Adiciona um delegado que será usado para configurar um nome HttpClient . |
ConfigurePrimaryHttpMessageHandler | Configura o primário HttpMessageHandler do contêiner de injeção de dependência para um arquivo 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 instância nova ou adicionada SocketsHttpHandler anteriormente do contêiner de injeção de dependência para ser usada como um manipulador primário para um arquivo HttpClient . (Somente .NET 5+) |
Usando IHttpClientFactory junto com SocketsHttpHandler
A SocketsHttpHandler
implementação do foi adicionada HttpMessageHandler
no .NET Core 2.1, que permite PooledConnectionLifetime
ser configurado. Essa configuração é usada para garantir que o manipulador reaja às alterações de DNS, portanto, o uso SocketsHttpHandler
é considerado uma alternativa ao uso IHttpClientFactory
do . 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 Configure com base no intervalo que você 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 conexões e reciclagem, a reciclagem doIHttpClientFactory
manipulador no nível não é mais 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 digitados em serviços singleton
Ao usar a abordagem de cliente nomeado, IHttpClientFactory
é injetado em serviços, e HttpClient
as instâncias são criadas chamando CreateClient sempre que é HttpClient
necessário.
No entanto, com a abordagem de cliente digitado, os clientes digitados são objetos transitórios geralmente 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 ela reaja às alterações de DNS, derrotando uma das finalidades do IHttpClientFactory
.
Se você precisar usar HttpClient
instâncias em um serviço singleton, considere as seguintes opções:
- Em vez disso, use a abordagem de cliente nomeado, injetando
IHttpClientFactory
o serviço singleton e recriandoHttpClient
instâncias quando necessário. - Se você precisar da abordagem de cliente digitada, use
SocketsHttpHandler
com configuradoPooledConnectionLifetime
como um manipulador primário. Para obter mais informações sobre como usarSocketsHttpHandler
comIHttpClientFactory
o , 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 ) dentro HttpMessageHandler
de instâncias e usar dependências com escopo com cuidado para evitar o vazamento de HttpContext
informações confidenciais.
Se você precisar de acesso a um escopo de DI de aplicativo do seu manipulador de mensagens, para autenticação como exemplo, você encapsularia a lógica com reconhecimento de escopo em um transitório DelegatingHandler
separado e a encapsularia em torno de uma HttpMessageHandler
instância do IHttpClientFactory
cache. Para acessar a chamada IHttpMessageHandlerFactory.CreateHandler do manipulador 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 reconhecimento de DelegatingHandler
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 outra solução alternativa pode ser seguida com um método de extensão para registrar um registro padrão com reconhecimento de DelegatingHandler
escopo e substituir IHttpClientFactory
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 obter mais informações, consulte o exemplo completo.