Fazer solicitações HTTP usando IHttpClientFactory no ASP.NET Core
Observação
Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Por Kirk Larkin, Steve Gordon, Glenn Condron e Ryan Nowak.
É possível registrar e usar um IHttpClientFactory para configurar e criar instâncias de HttpClient em um aplicativo. IHttpClientFactory
oferece os seguintes benefícios:
- Fornece um local central para nomear e configurar instâncias lógicas de
HttpClient
. Por exemplo, um cliente chamado github pode ser registrado e configurado para acessar o GitHub. Um cliente padrão pode ser registrado para ter acesso geral. - Codifica o conceito de middleware de saída por meio da delegação de manipuladores em
HttpClient
. Fornece extensões para o middleware baseado em Polly para aproveitar a delegação de manipuladores emHttpClient
. - Gerencia o pooling e o tempo de vida das instâncias
HttpClientMessageHandler
subjacentes. O gerenciamento automático evita problemas comuns do DNS (Sistema de Nomes de Domínio) que ocorrem ao gerenciar manualmente os tempos de vidaHttpClient
. - 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.
O código de exemplo nesta versão do tópico usa System.Text.Json para desserializar o conteúdo JSON retornado em respostas HTTP. Para exemplos que usam Json.NET
e ReadAsAsync<T>
, use o seletor de versão a fim de selecionar uma versão 2.x deste tópico.
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
Registre IHttpClientFactory
chamando AddHttpClient
no Program.cs
:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient();
Um IHttpClientFactory
pode ser solicitado com o uso de DI (injeção de dependência). O código a seguir usa IHttpClientFactory
para criar uma instância HttpClient
.
public class BasicModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public BasicModel(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ HeaderNames.Accept, "application/vnd.github.v3+json" },
{ HeaderNames.UserAgent, "HttpRequestsSample" }
}
};
var httpClient = _httpClientFactory.CreateClient();
var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
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
. - Muitos
HttpClient
s têm configuração diferente.
Especifique a configuração de um HttpClient
nomeado durante seu registro no Program.cs
:
builder.Services.AddHttpClient("GitHub", httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});
No código anterior, o cliente é configurado com:
- O endereço básico
https://api.github.com/
. - Dois cabeçalhos exigidos para trabalhar com a API do GitHub.
CreateClient
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
:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public NamedClientModel(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
var httpClient = _httpClientFactory.CreateClient("GitHub");
var httpResponseMessage = await httpClient.GetAsync(
"repos/dotnet/AspNetCore.Docs/branches");
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
No código anterior, a solicitação 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:
public class GitHubService
{
private readonly HttpClient _httpClient;
public GitHubService(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
}
public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync() =>
await _httpClient.GetFromJsonAsync<IEnumerable<GitHubBranch>>(
"repos/dotnet/AspNetCore.Docs/branches");
}
No código anterior:
- A configuração é movida para o cliente tipado.
- A instância
HttpClient
fornecida é armazenada como um campo privado.
Métodos específicos da API podem ser criados que expõem a funcionalidade HttpClient
. Por exemplo, o método GetAspNetCoreDocsBranches
encapsula o código para recuperar branches do GitHub de documentos.
O código a seguir chamaAddHttpClient em Program.cs
para registrar uma classe de cliente tipado GitHubService
:
builder.Services.AddHttpClient<GitHubService>();
O cliente com tipo é registrado como transitório com a DI. No código anterior, AddHttpClient
registra GitHubService
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
GitHubService
, passando a instância deHttpClient
para seu construtor.
O cliente com tipo pode ser injetado e consumido diretamente:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public TypedClientModel(GitHubService gitHubService) =>
_gitHubService = gitHubService;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
GitHubBranches = await _gitHubService.GetAspNetCoreDocsBranchesAsync();
}
catch (HttpRequestException)
{
// ...
}
}
}
Se preferir, a configuração de um cliente com tipo também poderá ser especificada durante o registro em Program.cs
e não no construtor do cliente com tipo:
builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// ...
});
Clientes gerados
IHttpClientFactory
pode ser usado em combinação com bibliotecas de terceiros, como a Refit. A readequação é uma biblioteca REST para .NET. Ela converte APIs REST em interfaces dinâmicas. Chame AddRefitClient
para gerar uma implementação dinâmica de uma interface, que usa HttpClient
para fazer as chamadas HTTP externas.
Uma interface personalizada representa a API externa:
public interface IGitHubClient
{
[Get("/repos/dotnet/AspNetCore.Docs/branches")]
Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}
Chame AddRefitClient
para gerar a implementação dinâmica e, em seguida, chame ConfigureHttpClient
para configurar o HttpClient
subjacente:
builder.Services.AddRefitClient<IGitHubClient>()
.ConfigureHttpClient(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});
Use a DI para acessar a implementação dinâmica de IGitHubClient
:
public class RefitModel : PageModel
{
private readonly IGitHubClient _gitHubClient;
public RefitModel(IGitHubClient gitHubClient) =>
_gitHubClient = gitHubClient;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
GitHubBranches = await _gitHubClient.GetAspNetCoreDocsBranchesAsync();
}
catch (ApiException)
{
// ...
}
}
}
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.
O exemplo abaixo mostra como fazer uma solicitação HTTP POST:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json); // using static System.Net.Mime.MediaTypeNames;
using var httpResponseMessage =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponseMessage.EnsureSuccessStatusCode();
}
No código anterior, o método CreateItemAsync
:
- Serializa o parâmetro
TodoItem
para JSON usandoSystem.Text.Json
. - 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 abaixo mostra uma solicitação HTTP PUT:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json);
using var httpResponseMessage =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponseMessage.EnsureSuccessStatusCode();
}
O código anterior é semelhante ao exemplo POST. O método SaveItemAsync
chama PutAsync em vez de PostAsync
.
O exemplo abaixo mostra uma solicitação HTTP DELETE:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponseMessage =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponseMessage.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.
Middleware de solicitação de saída
O HttpClient
tem o conceito de delegar manipuladores que podem ser vinculados para solicitações HTTP de saída. IHttpClientFactory
:
- Simplifica a definição dos manipuladores a serem aplicados a cada cliente nomeado.
- Ele permite o registro e o encadeamento de vários manipuladores para criar um pipeline do middleware de solicitação saída. Cada um desses manipuladores é capaz de executar o trabalho antes e após a solicitação de saída. Este padrão:
- É semelhante ao pipeline do middleware de entrada no ASP.NET Core.
- Fornece um mecanismo para gerenciar questões entre cortes relativas a solicitações HTTP, por exemplo:
- cache
- tratamento de erros
- serialização
- registro em log
Para criar um identificador de delegação:
- Derive de DelegatingHandler.
- Substitua SendAsync. Execute o código antes de passar a solicitação para o próximo manipulador no pipeline:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"The API key header X-API-KEY is required.")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
O código anterior verifica se o cabeçalho X-API-KEY
está na solicitação. Se X-API-KEY
estiver ausente, BadRequest será retornado.
Mais de um manipulador pode ser adicionado à configuração de um HttpClient
com Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
builder.Services.AddTransient<ValidateHeaderHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<ValidateHeaderHandler>();
No código anterior, o ValidateHeaderHandler
é registrado com a DI. Depois de registrado, o AddHttpMessageHandler poderá ser chamado, passando o tipo para o manipulador.
Vários manipuladores podem ser registrados na ordem em que eles devem ser executados. Cada manipulador encapsula o próximo manipulador até que o HttpClientHandler
final execute a solicitação:
builder.Services.AddTransient<SampleHandler1>();
builder.Services.AddTransient<SampleHandler2>();
builder.Services.AddHttpClient("MultipleHttpMessageHandlers")
.AddHttpMessageHandler<SampleHandler1>()
.AddHttpMessageHandler<SampleHandler2>();
No código anterior, SampleHandler1
é executado primeiro, antes de SampleHandler2
.
Usar DI no middleware de solicitação de saída
Quando IHttpClientFactory
cria um manipulador de delegação, ele usa DI para atender aos parâmetros do construtor do manipulador. IHttpClientFactory
cria um escopo de DI separado para cada manipulador, o que pode levar a um comportamento surpreendente quando um manipulador consome um serviço com escopo.
Por exemplo, considere a seguinte interface e sua implementação, que representa uma tarefa como uma operação com um manipulador, OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Como o nome sugere, IOperationScoped
é registrado com DI usando um tempo de vida com escopo:
builder.Services.AddScoped<IOperationScoped, OperationScoped>();
O seguinte manipulador de delegação consome e usa IOperationScoped
a fim de definir o cabeçalho X-OPERATION-ID
para a solicitação de saída:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationScoped;
public OperationHandler(IOperationScoped operationScoped) =>
_operationScoped = operationScoped;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationScoped.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
No download de HttpRequestsSample
, navegue até /Operation
e atualize a página. O valor do escopo da solicitação é alterado para cada solicitação, mas o valor do escopo do manipulador muda apenas a cada 5 segundos.
Os manipuladores podem depender de serviços de qualquer escopo. Os serviços dos quais os manipuladores dependem são descartados quando o manipulador é descartado.
Use uma das seguintes abordagens para compartilhar o estado por solicitação com os manipuladores de mensagens:
- Passe os dados pelo manipulador usando HttpRequestMessage.Options.
- Use IHttpContextAccessor para acessar a solicitação atual.
- Crie um objeto de armazenamento AsyncLocal<T> personalizado para passar os dados.
Usar manipuladores baseados no Polly
IHttpClientFactory
integra-se à biblioteca de terceiros Polly. O Polly é uma biblioteca abrangente de tratamento de falha transitória e de resiliência para .NET. Ela permite que os desenvolvedores expressem políticas, como Repetição, Disjuntor, Tempo Limite, Isolamento de Bulkhead e Fallback de maneira fluente e thread-safe.
Os métodos de extensão são fornecidos para habilitar o uso de políticas do Polly com instâncias de HttpClient
configuradas. As extensões do Polly dão suporte à adição de manipuladores baseados em Polly aos clientes. Polly requer o pacote NuGet Microsoft.Extensions.Http.Polly.
Tratar falhas transitórias
As falhas normalmente ocorrem quando as chamadas HTTP externas são transitórias. AddTransientHttpErrorPolicy permite que uma política seja definida para lidar com erros transitórios. As políticas configuradas com AddTransientHttpErrorPolicy
manipulam as seguintes respostas:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
fornece acesso a um objeto PolicyBuilder
configurado para tratar erros que representam uma possível falha transitória:
builder.Services.AddHttpClient("PollyWaitAndRetry")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3, retryNumber => TimeSpan.FromMilliseconds(600)));
No código anterior, uma política WaitAndRetryAsync
é definida. As solicitações com falha são repetidas até três vezes com um atraso de 600 ms entre as tentativas.
Selecionar políticas dinamicamente
Métodos de extensão são fornecidos para adicionar manipuladores baseados em Polly, por exemplo, AddPolicyHandler. A seguinte sobrecarga de AddPolicyHandler
inspeciona a solicitação para decidir qual política aplicar:
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
builder.Services.AddHttpClient("PollyDynamic")
.AddPolicyHandler(httpRequestMessage =>
httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy : longTimeoutPolicy);
No código anterior, se a solicitação de saída é um HTTP GET, um tempo limite de 10 segundos é aplicado. Para qualquer outro método HTTP, é usado um tempo limite de 30 segundos.
Adicionar vários manipuladores do Polly
É comum aninhar políticas Polly:
builder.Services.AddHttpClient("PollyMultiple")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.RetryAsync(3))
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
No exemplo anterior:
- Dois manipuladores são adicionados.
- O primeiro manipulador usa AddTransientHttpErrorPolicy para adicionar uma política de repetição. As solicitações com falha são repetidas até três vezes.
- A segunda chamada
AddTransientHttpErrorPolicy
adiciona uma política de disjuntor. As solicitações externas adicionais são bloqueadas por 30 segundos quando ocorrem 5 tentativas com falha consecutivamente. As políticas de disjuntor são políticas com estado. Todas as chamadas por meio desse cliente compartilham o mesmo estado do circuito.
Adicionar políticas do registro do Polly
Uma abordagem para gerenciar as políticas usadas com frequência é defini-las uma única vez e registrá-las em um PolicyRegistry
. Por exemplo:
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var policyRegistry = builder.Services.AddPolicyRegistry();
policyRegistry.Add("Regular", timeoutPolicy);
policyRegistry.Add("Long", longTimeoutPolicy);
builder.Services.AddHttpClient("PollyRegistryRegular")
.AddPolicyHandlerFromRegistry("Regular");
builder.Services.AddHttpClient("PollyRegistryLong")
.AddPolicyHandlerFromRegistry("Long");
No código anterior:
- Duas políticas,
Regular
eLong
, são adicionadas ao registro do Polly. - AddPolicyHandlerFromRegistry configura clientes nomeados individuais para usar essas políticas do registro do Polly.
Para obter mais informações sobre as integrações IHttpClientFactory
e Polly, confira o wiki do Polly.
HttpClient e gerenciamento de tempo de vida
Uma nova instância de HttpClient
é chamada, sempre que CreateClient
é chamado na IHttpClientFactory
. Um HttpMessageHandler é criado por cliente nomeado. O alocador gerencia a vida útil das instâncias do HttpMessageHandler
.
IHttpClientFactory
cria pools com as instâncias de HttpMessageHandler
criadas pelo alocador para reduzir o consumo de recursos. Uma instância de HttpMessageHandler
poderá ser reutilizada no pool, ao criar uma nova instância de HttpClient
, se o respectivo tempo de vida não tiver expirado.
O pooling de manipuladores é preferível porque normalmente cada manipulador gerencia as próprias conexões HTTP subjacentes. Criar mais manipuladores do que o necessário pode resultar 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 (Sistema de Nomes de Domínio).
O tempo de vida padrão do manipulador é de 2 minutos. O valor padrão pode ser substituído para cada cliente nomeado:
builder.Services.AddHttpClient("HandlerLifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Geralmente, as instâncias de HttpClient
podem ser tratadas como objetos do .NET que não exigem descarte. O descarte cancela as solicitações de saída e garante que a determinada instância de HttpClient
não seja usada depois de chamar Dispose. IHttpClientFactory
rastreia e descarta recursos usados pelas instâncias de HttpClient
.
Manter uma única instância de HttpClient
ativa por uma longa duração é um padrão comum usado, antes do início de IHttpClientFactory
. Esse padrão se torna desnecessário após a migração para IHttpClientFactory
.
Alternativas ao IHttpClientFactory
O uso de IHttpClientFactory
em um aplicativo habilitado para DI evita:
- Problemas de esgotamento de recursos ao agrupar instâncias
HttpMessageHandler
. - Problemas de DNS obsoletos com a reciclagem de instâncias
HttpMessageHandler
em intervalos regulares.
Há maneiras alternativas de resolver os problemas anteriores usando uma instância de SocketsHttpHandler de longa duração.
- Crie uma instância de
SocketsHttpHandler
quando o aplicativo iniciar e use-a na vida útil do aplicativo. - Configure PooledConnectionLifetime com um valor apropriado com base nos tempos de atualização de DNS.
- Crie instâncias
HttpClient
usandonew HttpClient(handler, disposeHandler: false)
conforme a necessidade.
As abordagens anteriores resolvem os problemas de gerenciamento de recursos que IHttpClientFactory
resolve de maneira semelhante.
- O
SocketsHttpHandler
compartilha conexões entre instâncias deHttpClient
. Esse compartilhamento impede o esgotamento do soquete. - O
SocketsHttpHandler
recicla conexões de acordo comPooledConnectionLifetime
para evitar problemas de DNS obsoletos.
Log
Os clientes criado pelo IHttpClientFactory
registram mensagens de log para todas as solicitações. Habilite o nível apropriado de informações na configuração de log para ver as mensagens de log padrão. Os registros em log adicionais, como o registro em log dos cabeçalhos de solicitação, estão incluídos somente no nível de rastreamento.
A categoria de log usada para cada cliente inclui o nome do cliente. Um cliente chamado MyNamedClient, por exemplo, registra mensagens com uma categoria de "System.Net.Http.HttpClient.MyNamedClient.LogicalHandler". Mensagens sufixadas com LogicalHandler ocorrem fora do pipeline do manipulador de solicitações. Na solicitação, as mensagens são registradas em log antes que qualquer outro manipulador no pipeline a tenha processado. Na resposta, as mensagens são registradas depois que qualquer outro manipulador do pipeline tenha recebido a resposta.
O registro em log também ocorre dentro do pipeline do manipulador de solicitações. No exemplo MyNamedClient, essas mensagens são registradas com a categoria de log "System.Net.Http.HttpClient.MyNamedClient.ClientHandler". Para a solicitação, isso ocorre depois que todos os outros manipuladores são executados e imediatamente antes que a solicitação seja enviada. Na resposta, esse registro em log inclui o estado da resposta antes que ela seja retornada por meio do pipeline do manipulador.
Habilitar o registro em log dentro e fora do pipeline permite a inspeção das alterações feitas por outros manipuladores do pipeline. Isso pode incluir alterações nos cabeçalhos de solicitação ou no código de status da resposta.
Incluir o nome do cliente na categoria de log permite a filtragem de log para clientes nomeados específicos.
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. O representante que é usado para criar e configurar o HttpMessageHandler
primário usado pelo cliente:
builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
AllowAutoRedirect = true,
UseDefaultCredentials = true
});
Cookies
As instâncias de HttpMessageHandler
em pool resultam no compartilhamento de CookieContainer
. O compartilhamento do objeto CookieContainer
de forma inesperada geralmente resulta em código incorreto. Para aplicativos que exigem cookies, considere:
- Desabilitar o tratamento automático de cookie
- Evitar
IHttpClientFactory
Chamar ConfigurePrimaryHttpMessageHandler para desabilitar o tratamento automático de cookie:
builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});
Usar IHttpClientFactory em um aplicativo de console
Em um aplicativo de console, adicione as seguintes referências de pacote ao projeto:
No exemplo a seguir:
- IHttpClientFactory e
GitHubService
são registrados no contêiner de serviço do Host genérico. GitHubService
é solicitado da DI, que, por sua vez, solicita uma instância deIHttpClientFactory
.GitHubService
usaIHttpClientFactory
para criar uma instância deHttpClient
, que a usa para recuperar branches do GitHub do docs.
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var host = new HostBuilder()
.ConfigureServices(services =>
{
services.AddHttpClient();
services.AddTransient<GitHubService>();
})
.Build();
try
{
var gitHubService = host.Services.GetRequiredService<GitHubService>();
var gitHubBranches = await gitHubService.GetAspNetCoreDocsBranchesAsync();
Console.WriteLine($"{gitHubBranches?.Count() ?? 0} GitHub Branches");
if (gitHubBranches is not null)
{
foreach (var gitHubBranch in gitHubBranches)
{
Console.WriteLine($"- {gitHubBranch.Name}");
}
}
}
catch (Exception ex)
{
host.Services.GetRequiredService<ILogger<Program>>()
.LogError(ex, "Unable to load branches from GitHub.");
}
public class GitHubService
{
private readonly IHttpClientFactory _httpClientFactory;
public GitHubService(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ "Accept", "application/vnd.github.v3+json" },
{ "User-Agent", "HttpRequestsConsoleSample" }
}
};
var httpClient = _httpClientFactory.CreateClient();
var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
httpResponseMessage.EnsureSuccessStatusCode();
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
public record GitHubBranch(
[property: JsonPropertyName("name")] string Name);
Middleware de propagação de cabeçalho
A propagação de cabeçalho é um middleware ASP.NET Core que propaga cabeçalhos HTTP da solicitação de entrada para as solicitações HttpClient
de saída. Para usar a propagação de cabeçalho:
Instale o pacote Microsoft.AspNetCore.HeaderPropagation.
Configure o
HttpClient
e o pipeline de middleware emProgram.cs
:// Add services to the container. builder.Services.AddControllers(); builder.Services.AddHttpClient("PropagateHeaders") .AddHeaderPropagation(); builder.Services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.MapControllers();
Faça solicitações de saída usando a instância de
HttpClient
configurada, que inclui os cabeçalhos adicionados.
Recursos adicionais
- Exibir ou baixar código de exemplo (como baixar)
- Usar HttpClientFactory implementar solicitações HTTP resilientes
- Implementar repetições de chamadas HTTP com retirada exponencial com o HttpClientFactory e políticas da Polly
- Implementar o padrão de disjuntor
- Como serializar e desserializar JSON no .NET
Por Kirk Larkin, Steve Gordon, Glenn Condron e Ryan Nowak.
É possível registrar e usar um IHttpClientFactory para configurar e criar instâncias de HttpClient em um aplicativo. IHttpClientFactory
oferece os seguintes benefícios:
- Fornece um local central para nomear e configurar instâncias lógicas de
HttpClient
. Por exemplo, um cliente chamado github pode ser registrado e configurado para acessar o GitHub. Um cliente padrão pode ser registrado para ter acesso geral. - Codifica o conceito de middleware de saída por meio da delegação de manipuladores em
HttpClient
. Fornece extensões para o middleware baseado em Polly para aproveitar a delegação de manipuladores emHttpClient
. - Gerencia o pooling e o tempo de vida das instâncias
HttpClientMessageHandler
subjacentes. O gerenciamento automático evita problemas comuns do DNS (Sistema de Nomes de Domínio) que ocorrem ao gerenciar manualmente os tempos de vidaHttpClient
. - 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.
Exibir ou baixar um código de exemplo (como baixar).
O código de exemplo nesta versão do tópico usa System.Text.Json para desserializar o conteúdo JSON retornado em respostas HTTP. Para exemplos que usam Json.NET
e ReadAsAsync<T>
, use o seletor de versão a fim de selecionar uma versão 2.x deste tópico.
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
IHttpClientFactory
pode ser registrado chamando AddHttpClient
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
// Remaining code deleted for brevity.
Um IHttpClientFactory
pode ser solicitado com o uso de DI (injeção de dependência). O código a seguir usa IHttpClientFactory
para criar uma instância HttpClient
.
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
Branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
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
. - Muitos
HttpClient
s têm configuração diferente.
A configuração de um HttpClient
nomeado pode ser especificada durante o registro em Startup.ConfigureServices
:
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
No código anterior, o cliente é configurado com:
- O endereço básico
https://api.github.com/
. - Dois cabeçalhos exigidos para trabalhar com a API do GitHub.
CreateClient
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
:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
PullRequests = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubPullRequest>>(responseStream);
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
No código anterior, a solicitação 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:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
return await Client.GetFromJsonAsync<IEnumerable<GitHubIssue>>(
"/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
}
}
No código anterior:
- A configuração é movida para o cliente tipado.
- O objeto
HttpClient
é exposto como uma propriedade pública.
Métodos específicos da API podem ser criados que expõem a funcionalidade HttpClient
. Por exemplo, o método GetAspNetDocsIssues
encapsula o código para recuperar questões em aberto.
O código a seguir chamaAddHttpClient em Startup.ConfigureServices
para registrar uma classe de cliente com tipo:
services.AddHttpClient<GitHubService>();
O cliente com tipo é registrado como transitório com a DI. No código anterior, AddHttpClient
registra GitHubService
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
GitHubService
, passando a instância deHttpClient
para seu construtor.
O cliente com tipo pode ser injetado e consumido diretamente:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
A configuração de um cliente tipado poderá ser especificada durante o registro em Startup.ConfigureServices
e não no construtor do cliente com tipo:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
O HttpClient
pode ser encapsulado em um cliente tipado. Em vez de expô-la como uma propriedade, defina um método que chama a instância HttpClient
internamente:
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<string>>(responseStream);
}
}
No código anterior, o HttpClient
é armazenado em um campo privado. O acesso ao HttpClient
é pelo método GetRepos
público.
Clientes gerados
IHttpClientFactory
pode ser usado em combinação com bibliotecas de terceiros, como a Refit. A readequação é uma biblioteca REST para .NET. Ela converte APIs REST em interfaces dinâmicas. Uma implementação da interface é gerada dinamicamente pelo RestService
usando HttpClient
para fazer as chamadas de HTTP externas.
Uma interface e uma resposta são definidas para representar a API externa e sua resposta:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Um cliente com tipo pode ser adicionado usando a Refit para gerar a implementação:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddControllers();
}
A interface definida pode ser consumida, quando necessário, com a implementação fornecida pela DI e pela Refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
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.
O exemplo abaixo mostra como fazer uma solicitação HTTP POST:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
No código anterior, o método CreateItemAsync
:
- Serializa o parâmetro
TodoItem
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 abaixo mostra uma solicitação HTTP PUT:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
O código anterior é muito semelhante ao exemplo POST. O método SaveItemAsync
chama PutAsync em vez de PostAsync
.
O exemplo abaixo mostra uma solicitação HTTP DELETE:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
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.
Middleware de solicitação de saída
O HttpClient
tem o conceito de delegar manipuladores que podem ser vinculados para solicitações HTTP de saída. IHttpClientFactory
:
- Simplifica a definição dos manipuladores a serem aplicados a cada cliente nomeado.
- Ele permite o registro e o encadeamento de vários manipuladores para criar um pipeline do middleware de solicitação saída. Cada um desses manipuladores é capaz de executar o trabalho antes e após a solicitação de saída. Este padrão:
- É semelhante ao pipeline do middleware de entrada no ASP.NET Core.
- Fornece um mecanismo para gerenciar questões entre cortes relativas a solicitações HTTP, por exemplo:
- cache
- tratamento de erros
- serialização
- registro em log
Para criar um identificador de delegação:
- Derive de DelegatingHandler.
- Substitua SendAsync. Execute o código antes de passar a solicitação para o próximo manipulador no pipeline:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
O código anterior verifica se o cabeçalho X-API-KEY
está na solicitação. Se X-API-KEY
estiver ausente, BadRequest será retornado.
Mais de um manipulador pode ser adicionado à configuração de um HttpClient
com Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5001/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
// Remaining code deleted for brevity.
No código anterior, o ValidateHeaderHandler
é registrado com a DI. Depois de registrado, o AddHttpMessageHandler poderá ser chamado, passando o tipo para o manipulador.
Vários manipuladores podem ser registrados na ordem em que eles devem ser executados. Cada manipulador encapsula o próximo manipulador até que o HttpClientHandler
final execute a solicitação:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Usar DI no middleware de solicitação de saída
Quando IHttpClientFactory
cria um manipulador de delegação, ele usa DI para atender aos parâmetros do construtor do manipulador. IHttpClientFactory
cria um escopo de DI separado para cada manipulador, o que pode levar a um comportamento surpreendente quando um manipulador consome um serviço com escopo.
Por exemplo, considere a seguinte interface e sua implementação, que representa uma tarefa como uma operação com um manipulador, OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Como o nome sugere, IOperationScoped
é registrado com DI usando um tempo de vida com escopo:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoItems"));
services.AddHttpContextAccessor();
services.AddHttpClient<TodoClient>((sp, httpClient) =>
{
var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;
// For sample purposes, assume TodoClient is used in the context of an incoming request.
httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
httpRequest.Host, httpRequest.PathBase));
httpClient.Timeout = TimeSpan.FromSeconds(5);
});
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddTransient<OperationHandler>();
services.AddTransient<OperationResponseHandler>();
services.AddHttpClient("Operation")
.AddHttpMessageHandler<OperationHandler>()
.AddHttpMessageHandler<OperationResponseHandler>()
.SetHandlerLifetime(TimeSpan.FromSeconds(5));
services.AddControllers();
services.AddRazorPages();
}
O seguinte manipulador de delegação consome e usa IOperationScoped
a fim de definir o cabeçalho X-OPERATION-ID
para a solicitação de saída:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationService;
public OperationHandler(IOperationScoped operationScoped)
{
_operationService = operationScoped;
}
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
No download de HttpRequestsSample
], navegue até /Operation
e atualize a página. O valor do escopo da solicitação é alterado para cada solicitação, mas o valor do escopo do manipulador muda apenas a cada 5 segundos.
Os manipuladores podem depender de serviços de qualquer escopo. Os serviços dos quais os manipuladores dependem são descartados quando o manipulador é descartado.
Use uma das seguintes abordagens para compartilhar o estado por solicitação com os manipuladores de mensagens:
- Passe os dados pelo manipulador usando HttpRequestMessage.Options.
- Use IHttpContextAccessor para acessar a solicitação atual.
- Crie um objeto de armazenamento AsyncLocal<T> personalizado para passar os dados.
Usar manipuladores baseados no Polly
IHttpClientFactory
integra-se à biblioteca de terceiros Polly. O Polly é uma biblioteca abrangente de tratamento de falha transitória e de resiliência para .NET. Ela permite que os desenvolvedores expressem políticas, como Repetição, Disjuntor, Tempo Limite, Isolamento de Bulkhead e Fallback de maneira fluente e thread-safe.
Os métodos de extensão são fornecidos para habilitar o uso de políticas do Polly com instâncias de HttpClient
configuradas. As extensões do Polly dão suporte à adição de manipuladores baseados em Polly aos clientes. Polly requer o pacote NuGet Microsoft.Extensions.Http.Polly.
Tratar falhas transitórias
As falhas normalmente ocorrem quando as chamadas HTTP externas são transitórias. AddTransientHttpErrorPolicy permite que uma política seja definida para lidar com erros transitórios. As políticas configuradas com AddTransientHttpErrorPolicy
manipulam as seguintes respostas:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
fornece acesso a um objeto PolicyBuilder
configurado para tratar erros que representam uma possível falha transitória:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
No código anterior, uma política WaitAndRetryAsync
é definida. As solicitações com falha são repetidas até três vezes com um atraso de 600 ms entre as tentativas.
Selecionar políticas dinamicamente
Métodos de extensão são fornecidos para adicionar manipuladores baseados em Polly, por exemplo, AddPolicyHandler. A seguinte sobrecarga de AddPolicyHandler
inspeciona a solicitação para decidir qual política aplicar:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
No código anterior, se a solicitação de saída é um HTTP GET, um tempo limite de 10 segundos é aplicado. Para qualquer outro método HTTP, é usado um tempo limite de 30 segundos.
Adicionar vários manipuladores do Polly
É comum aninhar políticas Polly:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
No exemplo anterior:
- Dois manipuladores são adicionados.
- O primeiro manipulador usa AddTransientHttpErrorPolicy para adicionar uma política de repetição. As solicitações com falha são repetidas até três vezes.
- A segunda chamada
AddTransientHttpErrorPolicy
adiciona uma política de disjuntor. As solicitações externas adicionais são bloqueadas por 30 segundos quando ocorrem 5 tentativas com falha consecutivamente. As políticas de disjuntor são políticas com estado. Todas as chamadas por meio desse cliente compartilham o mesmo estado do circuito.
Adicionar políticas do registro do Polly
Uma abordagem para gerenciar as políticas usadas com frequência é defini-las uma única vez e registrá-las em um PolicyRegistry
.
No seguinte código:
- As políticas "regulares" e "longas" são adicionadas.
- AddPolicyHandlerFromRegistry adiciona as políticas "regulares" e "longas" do registro.
public void ConfigureServices(IServiceCollection services)
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regularTimeoutHandler")
.AddPolicyHandlerFromRegistry("regular");
services.AddHttpClient("longTimeoutHandler")
.AddPolicyHandlerFromRegistry("long");
// Remaining code deleted for brevity.
Para obter mais informações sobre as integrações IHttpClientFactory
e Polly, confira o wiki do Polly.
HttpClient e gerenciamento de tempo de vida
Uma nova instância de HttpClient
é chamada, sempre que CreateClient
é chamado na IHttpClientFactory
. Um HttpMessageHandler é criado por cliente nomeado. O alocador gerencia a vida útil das instâncias do HttpMessageHandler
.
IHttpClientFactory
cria pools com as instâncias de HttpMessageHandler
criadas pelo alocador para reduzir o consumo de recursos. Uma instância de HttpMessageHandler
poderá ser reutilizada no pool, ao criar uma nova instância de HttpClient
, se o respectivo tempo de vida não tiver expirado.
O pooling de manipuladores é preferível porque normalmente cada manipulador gerencia as próprias conexões HTTP subjacentes. Criar mais manipuladores do que o necessário pode resultar 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 (Sistema de Nomes de Domínio).
O tempo de vida padrão do manipulador é de 2 minutos. O valor padrão pode ser substituído para cada cliente nomeado:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
Geralmente, as instâncias de HttpClient
podem ser tratadas como objetos do .NET que não exigem descarte. O descarte cancela as solicitações de saída e garante que a determinada instância de HttpClient
não seja usada depois de chamar Dispose. IHttpClientFactory
rastreia e descarta recursos usados pelas instâncias de HttpClient
.
Manter uma única instância de HttpClient
ativa por uma longa duração é um padrão comum usado, antes do início de IHttpClientFactory
. Esse padrão se torna desnecessário após a migração para IHttpClientFactory
.
Alternativas ao IHttpClientFactory
O uso de IHttpClientFactory
em um aplicativo habilitado para DI evita:
- Problemas de esgotamento de recursos ao agrupar instâncias
HttpMessageHandler
. - Problemas de DNS obsoletos com a reciclagem de instâncias
HttpMessageHandler
em intervalos regulares.
Há maneiras alternativas de resolver os problemas anteriores usando uma instância de SocketsHttpHandler de longa duração.
- Crie uma instância de
SocketsHttpHandler
quando o aplicativo iniciar e use-a na vida útil do aplicativo. - Configure PooledConnectionLifetime com um valor apropriado com base nos tempos de atualização de DNS.
- Crie instâncias
HttpClient
usandonew HttpClient(handler, disposeHandler: false)
conforme a necessidade.
As abordagens anteriores resolvem os problemas de gerenciamento de recursos que IHttpClientFactory
resolve de maneira semelhante.
- O
SocketsHttpHandler
compartilha conexões entre instâncias deHttpClient
. Esse compartilhamento impede o esgotamento do soquete. - O
SocketsHttpHandler
recicla conexões de acordo comPooledConnectionLifetime
para evitar problemas de DNS obsoletos.
Cookies
As instâncias de HttpMessageHandler
em pool resultam no compartilhamento de CookieContainer
. O compartilhamento do objeto CookieContainer
de forma inesperada geralmente resulta em código incorreto. Para aplicativos que exigem cookies, considere:
- Desabilitar o tratamento automático de cookie
- Evitar
IHttpClientFactory
Chamar ConfigurePrimaryHttpMessageHandler para desabilitar o tratamento automático de cookie:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Log
Os clientes criado pelo IHttpClientFactory
registram mensagens de log para todas as solicitações. Habilite o nível apropriado de informações na configuração de log para ver as mensagens de log padrão. Os registros em log adicionais, como o registro em log dos cabeçalhos de solicitação, estão incluídos somente no nível de rastreamento.
A categoria de log usada para cada cliente inclui o nome do cliente. Um cliente chamado MyNamedClient, por exemplo, registra mensagens com uma categoria de "System.Net.Http.HttpClient.MyNamedClient.LogicalHandler". Mensagens sufixadas com LogicalHandler ocorrem fora do pipeline do manipulador de solicitações. Na solicitação, as mensagens são registradas em log antes que qualquer outro manipulador no pipeline a tenha processado. Na resposta, as mensagens são registradas depois que qualquer outro manipulador do pipeline tenha recebido a resposta.
O registro em log também ocorre dentro do pipeline do manipulador de solicitações. No exemplo MyNamedClient, essas mensagens são registradas com a categoria de log "System.Net.Http.HttpClient.MyNamedClient.ClientHandler". Para a solicitação, isso ocorre depois que todos os outros manipuladores são executados e imediatamente antes que a solicitação seja enviada. Na resposta, esse registro em log inclui o estado da resposta antes que ela seja retornada por meio do pipeline do manipulador.
Habilitar o registro em log dentro e fora do pipeline permite a inspeção das alterações feitas por outros manipuladores do pipeline. Isso pode incluir alterações nos cabeçalhos de solicitação ou no código de status da resposta.
Incluir o nome do cliente na categoria de log permite a filtragem de log para clientes nomeados específicos.
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. O representante que é usado para criar e configurar o HttpMessageHandler
primário usado pelo cliente:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
Usar IHttpClientFactory em um aplicativo de console
Em um aplicativo de console, adicione as seguintes referências de pacote ao projeto:
No exemplo a seguir:
- IHttpClientFactory é registrado no contêiner de serviço do Host genérico.
MyService
cria uma instância de fábrica do cliente do serviço, que é usada para criar umHttpClient
.HttpClient
é usado para recuperar uma página da Web.Main
cria um escopo para executar o métodoGetPage
do serviço e gravar os primeiros 500 caracteres do conteúdo da página da Web no console.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware de propagação de cabeçalho
A propagação de cabeçalho é um middleware ASP.NET Core que propaga cabeçalhos HTTP da solicitação de entrada para as solicitações de cliente HTTP de saída. Para usar a propagação de cabeçalho:
Faça referência ao pacote Microsoft.AspNetCore.HeaderPropagation.
Configure o middleware e
HttpClient
emStartup
:public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
O cliente inclui os cabeçalhos configurados em solicitações de saída:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Recursos adicionais
Por Kirk Larkin, Steve Gordon, Glenn Condron e Ryan Nowak.
É possível registrar e usar um IHttpClientFactory para configurar e criar instâncias de HttpClient em um aplicativo. IHttpClientFactory
oferece os seguintes benefícios:
- Fornece um local central para nomear e configurar instâncias lógicas de
HttpClient
. Por exemplo, um cliente chamado github pode ser registrado e configurado para acessar o GitHub. Um cliente padrão pode ser registrado para ter acesso geral. - Codifica o conceito de middleware de saída por meio da delegação de manipuladores em
HttpClient
. Fornece extensões para o middleware baseado em Polly para aproveitar a delegação de manipuladores emHttpClient
. - Gerencia o pooling e o tempo de vida das instâncias
HttpClientMessageHandler
subjacentes. O gerenciamento automático evita problemas comuns do DNS (Sistema de Nomes de Domínio) que ocorrem ao gerenciar manualmente os tempos de vidaHttpClient
. - 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.
Exibir ou baixar um código de exemplo (como baixar).
O código de exemplo nesta versão do tópico usa System.Text.Json para desserializar o conteúdo JSON retornado em respostas HTTP. Para exemplos que usam Json.NET
e ReadAsAsync<T>
, use o seletor de versão a fim de selecionar uma versão 2.x deste tópico.
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
IHttpClientFactory
pode ser registrado chamando AddHttpClient
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
// Remaining code deleted for brevity.
Um IHttpClientFactory
pode ser solicitado com o uso de DI (injeção de dependência). O código a seguir usa IHttpClientFactory
para criar uma instância HttpClient
.
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
Branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
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
. - Muitos
HttpClient
s têm configuração diferente.
A configuração de um HttpClient
nomeado pode ser especificada durante o registro em Startup.ConfigureServices
:
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
No código anterior, o cliente é configurado com:
- O endereço básico
https://api.github.com/
. - Dois cabeçalhos exigidos para trabalhar com a API do GitHub.
CreateClient
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
:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
PullRequests = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubPullRequest>>(responseStream);
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
No código anterior, a solicitação 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:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await Client.GetAsync(
"/repos/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubIssue>>(responseStream);
}
}
Se você quiser ver os comentários de código traduzidos para idiomas diferentes do inglês, informe-nos neste problema de discussão do GitHub.
No código anterior:
- A configuração é movida para o cliente tipado.
- O objeto
HttpClient
é exposto como uma propriedade pública.
Métodos específicos da API podem ser criados que expõem a funcionalidade HttpClient
. Por exemplo, o método GetAspNetDocsIssues
encapsula o código para recuperar questões em aberto.
O código a seguir chamaAddHttpClient em Startup.ConfigureServices
para registrar uma classe de cliente com tipo:
services.AddHttpClient<GitHubService>();
O cliente com tipo é registrado como transitório com a DI. No código anterior, AddHttpClient
registra GitHubService
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
GitHubService
, passando a instância deHttpClient
para seu construtor.
O cliente com tipo pode ser injetado e consumido diretamente:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
A configuração de um cliente tipado poderá ser especificada durante o registro em Startup.ConfigureServices
e não no construtor do cliente com tipo:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
O HttpClient
pode ser encapsulado em um cliente tipado. Em vez de expô-la como uma propriedade, defina um método que chama a instância HttpClient
internamente:
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<string>>(responseStream);
}
}
No código anterior, o HttpClient
é armazenado em um campo privado. O acesso ao HttpClient
é pelo método GetRepos
público.
Clientes gerados
IHttpClientFactory
pode ser usado em combinação com bibliotecas de terceiros, como a Refit. A readequação é uma biblioteca REST para .NET. Ela converte APIs REST em interfaces dinâmicas. Uma implementação da interface é gerada dinamicamente pelo RestService
usando HttpClient
para fazer as chamadas de HTTP externas.
Uma interface e uma resposta são definidas para representar a API externa e sua resposta:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Um cliente com tipo pode ser adicionado usando a Refit para gerar a implementação:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddControllers();
}
A interface definida pode ser consumida, quando necessário, com a implementação fornecida pela DI e pela Refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
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.
O exemplo abaixo mostra como fazer uma solicitação HTTP POST:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
No código anterior, o método CreateItemAsync
:
- Serializa o parâmetro
TodoItem
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 abaixo mostra uma solicitação HTTP PUT:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
O código anterior é muito semelhante ao exemplo POST. O método SaveItemAsync
chama PutAsync em vez de PostAsync
.
O exemplo abaixo mostra uma solicitação HTTP DELETE:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
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.
Middleware de solicitação de saída
O HttpClient
tem o conceito de delegar manipuladores que podem ser vinculados para solicitações HTTP de saída. IHttpClientFactory
:
- Simplifica a definição dos manipuladores a serem aplicados a cada cliente nomeado.
- Ele permite o registro e o encadeamento de vários manipuladores para criar um pipeline do middleware de solicitação saída. Cada um desses manipuladores é capaz de executar o trabalho antes e após a solicitação de saída. Este padrão:
- É semelhante ao pipeline do middleware de entrada no ASP.NET Core.
- Fornece um mecanismo para gerenciar questões entre cortes relativas a solicitações HTTP, por exemplo:
- cache
- tratamento de erros
- serialização
- registro em log
Para criar um identificador de delegação:
- Derive de DelegatingHandler.
- Substitua SendAsync. Execute o código antes de passar a solicitação para o próximo manipulador no pipeline:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
O código anterior verifica se o cabeçalho X-API-KEY
está na solicitação. Se X-API-KEY
estiver ausente, BadRequest será retornado.
Mais de um manipulador pode ser adicionado à configuração de um HttpClient
com Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5001/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
// Remaining code deleted for brevity.
No código anterior, o ValidateHeaderHandler
é registrado com a DI. Depois de registrado, o AddHttpMessageHandler poderá ser chamado, passando o tipo para o manipulador.
Vários manipuladores podem ser registrados na ordem em que eles devem ser executados. Cada manipulador encapsula o próximo manipulador até que o HttpClientHandler
final execute a solicitação:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Usar DI no middleware de solicitação de saída
Quando IHttpClientFactory
cria um manipulador de delegação, ele usa DI para atender aos parâmetros do construtor do manipulador. IHttpClientFactory
cria um escopo de DI separado para cada manipulador, o que pode levar a um comportamento surpreendente quando um manipulador consome um serviço com escopo.
Por exemplo, considere a seguinte interface e sua implementação, que representa uma tarefa como uma operação com um manipulador, OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Como o nome sugere, IOperationScoped
é registrado com DI usando um tempo de vida com escopo:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoItems"));
services.AddHttpContextAccessor();
services.AddHttpClient<TodoClient>((sp, httpClient) =>
{
var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;
// For sample purposes, assume TodoClient is used in the context of an incoming request.
httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
httpRequest.Host, httpRequest.PathBase));
httpClient.Timeout = TimeSpan.FromSeconds(5);
});
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddTransient<OperationHandler>();
services.AddTransient<OperationResponseHandler>();
services.AddHttpClient("Operation")
.AddHttpMessageHandler<OperationHandler>()
.AddHttpMessageHandler<OperationResponseHandler>()
.SetHandlerLifetime(TimeSpan.FromSeconds(5));
services.AddControllers();
services.AddRazorPages();
}
O seguinte manipulador de delegação consome e usa IOperationScoped
a fim de definir o cabeçalho X-OPERATION-ID
para a solicitação de saída:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationService;
public OperationHandler(IOperationScoped operationScoped)
{
_operationService = operationScoped;
}
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
No download de HttpRequestsSample
], navegue até /Operation
e atualize a página. O valor do escopo da solicitação é alterado para cada solicitação, mas o valor do escopo do manipulador muda apenas a cada 5 segundos.
Os manipuladores podem depender de serviços de qualquer escopo. Os serviços dos quais os manipuladores dependem são descartados quando o manipulador é descartado.
Use uma das seguintes abordagens para compartilhar o estado por solicitação com os manipuladores de mensagens:
- Passe os dados pelo manipulador usando HttpRequestMessage.Properties.
- Use IHttpContextAccessor para acessar a solicitação atual.
- Crie um objeto de armazenamento AsyncLocal<T> personalizado para passar os dados.
Usar manipuladores baseados no Polly
IHttpClientFactory
integra-se à biblioteca de terceiros Polly. O Polly é uma biblioteca abrangente de tratamento de falha transitória e de resiliência para .NET. Ela permite que os desenvolvedores expressem políticas, como Repetição, Disjuntor, Tempo Limite, Isolamento de Bulkhead e Fallback de maneira fluente e thread-safe.
Os métodos de extensão são fornecidos para habilitar o uso de políticas do Polly com instâncias de HttpClient
configuradas. As extensões do Polly dão suporte à adição de manipuladores baseados em Polly aos clientes. Polly requer o pacote NuGet Microsoft.Extensions.Http.Polly.
Tratar falhas transitórias
As falhas normalmente ocorrem quando as chamadas HTTP externas são transitórias. AddTransientHttpErrorPolicy permite que uma política seja definida para lidar com erros transitórios. As políticas configuradas com AddTransientHttpErrorPolicy
manipulam as seguintes respostas:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
fornece acesso a um objeto PolicyBuilder
configurado para tratar erros que representam uma possível falha transitória:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
No código anterior, uma política WaitAndRetryAsync
é definida. As solicitações com falha são repetidas até três vezes com um atraso de 600 ms entre as tentativas.
Selecionar políticas dinamicamente
Métodos de extensão são fornecidos para adicionar manipuladores baseados em Polly, por exemplo, AddPolicyHandler. A seguinte sobrecarga de AddPolicyHandler
inspeciona a solicitação para decidir qual política aplicar:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
No código anterior, se a solicitação de saída é um HTTP GET, um tempo limite de 10 segundos é aplicado. Para qualquer outro método HTTP, é usado um tempo limite de 30 segundos.
Adicionar vários manipuladores do Polly
É comum aninhar políticas Polly:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
No exemplo anterior:
- Dois manipuladores são adicionados.
- O primeiro manipulador usa AddTransientHttpErrorPolicy para adicionar uma política de repetição. As solicitações com falha são repetidas até três vezes.
- A segunda chamada
AddTransientHttpErrorPolicy
adiciona uma política de disjuntor. As solicitações externas adicionais são bloqueadas por 30 segundos quando ocorrem 5 tentativas com falha consecutivamente. As políticas de disjuntor são políticas com estado. Todas as chamadas por meio desse cliente compartilham o mesmo estado do circuito.
Adicionar políticas do registro do Polly
Uma abordagem para gerenciar as políticas usadas com frequência é defini-las uma única vez e registrá-las em um PolicyRegistry
.
No seguinte código:
- As políticas "regulares" e "longas" são adicionadas.
- AddPolicyHandlerFromRegistry adiciona as políticas "regulares" e "longas" do registro.
public void ConfigureServices(IServiceCollection services)
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regularTimeoutHandler")
.AddPolicyHandlerFromRegistry("regular");
services.AddHttpClient("longTimeoutHandler")
.AddPolicyHandlerFromRegistry("long");
// Remaining code deleted for brevity.
Para obter mais informações sobre as integrações IHttpClientFactory
e Polly, confira o wiki do Polly.
HttpClient e gerenciamento de tempo de vida
Uma nova instância de HttpClient
é chamada, sempre que CreateClient
é chamado na IHttpClientFactory
. Um HttpMessageHandler é criado por cliente nomeado. O alocador gerencia a vida útil das instâncias do HttpMessageHandler
.
IHttpClientFactory
cria pools com as instâncias de HttpMessageHandler
criadas pelo alocador para reduzir o consumo de recursos. Uma instância de HttpMessageHandler
poderá ser reutilizada no pool, ao criar uma nova instância de HttpClient
, se o respectivo tempo de vida não tiver expirado.
O pooling de manipuladores é preferível porque normalmente cada manipulador gerencia as próprias conexões HTTP subjacentes. Criar mais manipuladores do que o necessário pode resultar 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 (Sistema de Nomes de Domínio).
O tempo de vida padrão do manipulador é de 2 minutos. O valor padrão pode ser substituído para cada cliente nomeado:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
Geralmente, as instâncias de HttpClient
podem ser tratadas como objetos do .NET que não exigem descarte. O descarte cancela as solicitações de saída e garante que a determinada instância de HttpClient
não seja usada depois de chamar Dispose. IHttpClientFactory
rastreia e descarta recursos usados pelas instâncias de HttpClient
.
Manter uma única instância de HttpClient
ativa por uma longa duração é um padrão comum usado, antes do início de IHttpClientFactory
. Esse padrão se torna desnecessário após a migração para IHttpClientFactory
.
Alternativas ao IHttpClientFactory
O uso de IHttpClientFactory
em um aplicativo habilitado para DI evita:
- Problemas de esgotamento de recursos ao agrupar instâncias
HttpMessageHandler
. - Problemas de DNS obsoletos com a reciclagem de instâncias
HttpMessageHandler
em intervalos regulares.
Há maneiras alternativas de resolver os problemas anteriores usando uma instância de SocketsHttpHandler de longa duração.
- Crie uma instância de
SocketsHttpHandler
quando o aplicativo iniciar e use-a na vida útil do aplicativo. - Configure PooledConnectionLifetime com um valor apropriado com base nos tempos de atualização de DNS.
- Crie instâncias
HttpClient
usandonew HttpClient(handler, disposeHandler: false)
conforme a necessidade.
As abordagens anteriores resolvem os problemas de gerenciamento de recursos que IHttpClientFactory
resolve de maneira semelhante.
- O
SocketsHttpHandler
compartilha conexões entre instâncias deHttpClient
. Esse compartilhamento impede o esgotamento do soquete. - O
SocketsHttpHandler
recicla conexões de acordo comPooledConnectionLifetime
para evitar problemas de DNS obsoletos.
Cookies
As instâncias de HttpMessageHandler
em pool resultam no compartilhamento de CookieContainer
. O compartilhamento do objeto CookieContainer
de forma inesperada geralmente resulta em código incorreto. Para aplicativos que exigem cookies, considere:
- Desabilitar o tratamento automático de cookie
- Evitar
IHttpClientFactory
Chamar ConfigurePrimaryHttpMessageHandler para desabilitar o tratamento automático de cookie:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Log
Os clientes criado pelo IHttpClientFactory
registram mensagens de log para todas as solicitações. Habilite o nível apropriado de informações na configuração de log para ver as mensagens de log padrão. Os registros em log adicionais, como o registro em log dos cabeçalhos de solicitação, estão incluídos somente no nível de rastreamento.
A categoria de log usada para cada cliente inclui o nome do cliente. Um cliente chamado MyNamedClient, por exemplo, registra mensagens com uma categoria de "System.Net.Http.HttpClient.MyNamedClient.LogicalHandler". Mensagens sufixadas com LogicalHandler ocorrem fora do pipeline do manipulador de solicitações. Na solicitação, as mensagens são registradas em log antes que qualquer outro manipulador no pipeline a tenha processado. Na resposta, as mensagens são registradas depois que qualquer outro manipulador do pipeline tenha recebido a resposta.
O registro em log também ocorre dentro do pipeline do manipulador de solicitações. No exemplo MyNamedClient, essas mensagens são registradas com a categoria de log "System.Net.Http.HttpClient.MyNamedClient.ClientHandler". Para a solicitação, isso ocorre depois que todos os outros manipuladores são executados e imediatamente antes que a solicitação seja enviada. Na resposta, esse registro em log inclui o estado da resposta antes que ela seja retornada por meio do pipeline do manipulador.
Habilitar o registro em log dentro e fora do pipeline permite a inspeção das alterações feitas por outros manipuladores do pipeline. Isso pode incluir alterações nos cabeçalhos de solicitação ou no código de status da resposta.
Incluir o nome do cliente na categoria de log permite a filtragem de log para clientes nomeados específicos.
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. O representante que é usado para criar e configurar o HttpMessageHandler
primário usado pelo cliente:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
Usar IHttpClientFactory em um aplicativo de console
Em um aplicativo de console, adicione as seguintes referências de pacote ao projeto:
No exemplo a seguir:
- IHttpClientFactory é registrado no contêiner de serviço do Host genérico.
MyService
cria uma instância de fábrica do cliente do serviço, que é usada para criar umHttpClient
.HttpClient
é usado para recuperar uma página da Web.Main
cria um escopo para executar o métodoGetPage
do serviço e gravar os primeiros 500 caracteres do conteúdo da página da Web no console.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware de propagação de cabeçalho
A propagação de cabeçalho é um middleware ASP.NET Core que propaga cabeçalhos HTTP da solicitação de entrada para as solicitações de cliente HTTP de saída. Para usar a propagação de cabeçalho:
Faça referência ao pacote Microsoft.AspNetCore.HeaderPropagation.
Configure o middleware e
HttpClient
emStartup
:public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
O cliente inclui os cabeçalhos configurados em solicitações de saída:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Recursos adicionais
Por Glenn Condron, Ryan Nowak e Steve Gordon
É possível registrar e usar um IHttpClientFactory para configurar e criar instâncias de HttpClient em um aplicativo. Ele oferece os seguintes benefícios:
- Fornece um local central para nomear e configurar instâncias lógicas de
HttpClient
. Por exemplo, um cliente do github pode ser registrado e configurado para acessar o GitHub. Um cliente padrão pode ser registrado para outras finalidades. - Codifica o conceito de middleware de saída por meio da delegação de manipuladores no
HttpClient
e fornece extensões para que o middleware baseado no Polly possa aproveitar esse recurso. - Gerencia o pooling e o tempo de vida das instâncias de
HttpClientMessageHandler
subjacentes para evitar problemas de DNS comuns que ocorrem no gerenciamento manual de tempos de vida deHttpClient
. - 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.
Exibir ou baixar código de exemplo (como baixar)
Pré-requisitos
Os projetos direcionados ao .NET Framework exigem a instalação do pacote do NuGet Microsoft.Extensions.Http. Os projetos destinados ao .Net Core e a referência ao metapacote Microsoft.AspNetCore.App já incluem o pacote Microsoft.Extensions.Http
.
Padrões de consumo
Há várias maneiras de usar o IHttpClientFactory
em um aplicativo:
Nenhum deles é estritamente superiores ao outro. A melhor abordagem depende das restrições do aplicativo.
Uso básico
O IHttpClientFactory
pode ser registrado chamando o método de extensão AddHttpClient
em IServiceCollection
, dentro do método Startup.ConfigureServices
.
services.AddHttpClient();
Depois de registrado, o código pode aceitar um IHttpClientFactory
em qualquer lugar em que os serviços possam ser injetados com injeção de dependência. O IHttpClientFactory
pode ser usado para criar uma instância de HttpClient
:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
Branches = await response.Content
.ReadAsAsync<IEnumerable<GitHubBranch>>();
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Usar o IHttpClientFactory
dessa forma é uma ótima maneira de refatorar um aplicativo existente. Não há nenhum impacto na maneira em que o HttpClient
é usado. Nos locais em que as instâncias de HttpClient
são criadas no momento, substitua essas ocorrências por uma chamada a CreateClient.
Clientes nomeados
Quando um aplicativo exige vários usos distintos do HttpClient
, cada um com uma configuração diferente, uma opção é usar clientes nomeados. A configuração de um HttpClient
nomeado pode ser especificada durante o registro em Startup.ConfigureServices
.
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
No código anterior, AddHttpClient
é chamado, fornecendo o nome github. Esse cliente tem algumas configurações padrão aplicadas, ou seja, o endereço básico e dois cabeçalhos necessários para trabalhar com a API do GitHub.
Toda vez que o CreateClient
é chamado, uma nova instância de HttpClient
é criada e a ação de configuração é chamada.
Para consumir um cliente nomeado, um parâmetro de cadeia de caracteres pode ser passado para CreateClient
. Especifique o nome do cliente a ser criado:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
PullRequests = await response.Content
.ReadAsAsync<IEnumerable<GitHubPullRequest>>();
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
No código anterior, a solicitação não precisa especificar um nome do host. Ela 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 e encapsular toda a lógica que lida com esse 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:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await Client.GetAsync(
"/repos/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadAsAsync<IEnumerable<GitHubIssue>>();
return result;
}
}
No código anterior, a configuração é movida para o cliente com tipo. O objeto HttpClient
é exposto como uma propriedade pública. É possível definir métodos específicos da API que expõem a funcionalidade HttpClient
. O método GetAspNetDocsIssues
encapsula o código necessário para consultar e analisar os últimos problemas em aberto de um repositório GitHub.
Para registrar um cliente com tipo, o método de extensão AddHttpClient genérico pode ser usado em Startup.ConfigureServices
, especificando a classe do cliente com tipo:
services.AddHttpClient<GitHubService>();
O cliente com tipo é registrado como transitório com a DI. O cliente com tipo pode ser injetado e consumido diretamente:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
Se preferir, a configuração de um cliente com tipo poderá ser especificada durante o registro em Startup.ConfigureServices
e não no construtor do cliente com tipo:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
É possível encapsular totalmente o HttpClient
dentro de um cliente com tipo. Em vez de o expor como uma propriedade, é possível fornecer métodos públicos que chamam a instância de HttpClient
internamente.
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadAsAsync<IEnumerable<string>>();
return result;
}
}
No código anterior, o HttpClient
é armazenado como um campo privado. Todo o acesso para fazer chamadas externas passa pelo método GetRepos
.
Clientes gerados
O IHttpClientFactory
pode ser usado em combinação com outras bibliotecas de terceiros, como a Refit. A readequação é uma biblioteca REST para .NET. Ela converte APIs REST em interfaces dinâmicas. Uma implementação da interface é gerada dinamicamente pelo RestService
usando HttpClient
para fazer as chamadas de HTTP externas.
Uma interface e uma resposta são definidas para representar a API externa e sua resposta:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Um cliente com tipo pode ser adicionado usando a Refit para gerar a implementação:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddMvc();
}
A interface definida pode ser consumida, quando necessário, com a implementação fornecida pela DI e pela Refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
Middleware de solicitação de saída
O HttpClient
já tem o conceito de delegar manipuladores que podem ser vinculados para solicitações HTTP de saída. O IHttpClientFactory
facilita a definição dos manipuladores a serem aplicados a cada cliente nomeado. Ele permite o registro e o encadeamento de vários manipuladores para criar um pipeline do middleware de solicitação saída. Cada um desses manipuladores é capaz de executar o trabalho antes e após a solicitação de saída. Esse padrão é semelhante ao pipeline do middleware de entrada no ASP.NET Core. O padrão fornece um mecanismo para gerenciar interesses paralelos em relação às solicitações HTTP, incluindo o armazenamento em cache, o tratamento de erro, a serialização e o registro em log.
Para criar um manipulador, defina uma classe derivando-a de DelegatingHandler. Substitua o método SendAsync
para executar o código antes de passar a solicitação para o próximo manipulador no pipeline:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
O código anterior define um manipulador básico. Ele verifica se um cabeçalho X-API-KEY
foi incluído na solicitação. Se o cabeçalho estiver ausente, isso poderá evitar a chamada de HTTP e retornar uma resposta adequada.
Durante o registro, um ou mais manipuladores podem ser adicionados à configuração de um HttpClient
. Essa tarefa é realizada por meio de métodos de extensão no IHttpClientBuilder.
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5000/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
No código anterior, o ValidateHeaderHandler
é registrado com a DI. O manipulador deve ser registrado na injeção de dependência como serviço temporário, mas nunca com escopo. Se o manipulador estiver registrado como um serviço com escopo e algum serviço do qual ele depende for descartável:
- Os serviços do manipulador poderão ser descartados antes que ele saia do escopo.
- Os serviços de manipulador descartados farão com que o manipulador falhe.
Depois de registrado, o AddHttpMessageHandler poderá ser chamado, passando no tipo do manipulador.
Vários manipuladores podem ser registrados na ordem em que eles devem ser executados. Cada manipulador encapsula o próximo manipulador até que o HttpClientHandler
final execute a solicitação:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Use uma das seguintes abordagens para compartilhar o estado por solicitação com os manipuladores de mensagens:
- Passe os dados pelo manipulador usando
HttpRequestMessage.Properties
. - Use
IHttpContextAccessor
para acessar a solicitação atual. - Crie um objeto de armazenamento
AsyncLocal
personalizado para passar os dados.
Usar manipuladores baseados no Polly
O IHttpClientFactory
integra-se a uma biblioteca de terceiros popular chamada Polly. O Polly é uma biblioteca abrangente de tratamento de falha transitória e de resiliência para .NET. Ela permite que os desenvolvedores expressem políticas, como Repetição, Disjuntor, Tempo Limite, Isolamento de Bulkhead e Fallback de maneira fluente e thread-safe.
Os métodos de extensão são fornecidos para habilitar o uso de políticas do Polly com instâncias de HttpClient
configuradas. As extensões do Polly:
- Dão suporte à adição de manipuladores baseados no Polly aos clientes.
- Podem ser usadas depois de instalar o pacote do NuGet Microsoft.Extensions.Http.Polly. O pacote não está incluído na estrutura ASP.NET Core compartilhada.
Tratar falhas transitórias
As falhas mais comuns ocorrem quando as chamadas HTTP externas são transitórias. Um método de extensão conveniente chamado AddTransientHttpErrorPolicy
é incluído para permitir que uma política seja definido para tratar erros transitórios. As políticas configuradas com esse método de extensão tratam HttpRequestException
, respostas HTTP 5xx e respostas HTTP 408.
A extensão AddTransientHttpErrorPolicy
pode ser usada em Startup.ConfigureServices
. A extensão fornece acesso a um objeto PolicyBuilder
configurado para tratar erros que representam uma possível falha transitória:
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
No código anterior, uma política WaitAndRetryAsync
é definida. As solicitações com falha são repetidas até três vezes com um atraso de 600 ms entre as tentativas.
Selecionar políticas dinamicamente
Existem métodos de extensão adicionais que podem ser usados para adicionar manipuladores baseados no Polly. Uma dessas extensões é a AddPolicyHandler
, que tem várias sobrecargas. Uma sobrecarga permite que a solicitação seja inspecionada durante a definição da política a ser aplicada:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
No código anterior, se a solicitação de saída é um HTTP GET, um tempo limite de 10 segundos é aplicado. Para qualquer outro método HTTP, é usado um tempo limite de 30 segundos.
Adicionar vários manipuladores do Polly
É comum aninhar políticas do Polly para fornecer funcionalidade avançada:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
No exemplo anterior, dois manipuladores são adicionados. O primeiro usa a extensão AddTransientHttpErrorPolicy
para adicionar uma política de repetição. As solicitações com falha são repetidas até três vezes. A segunda chamada a AddTransientHttpErrorPolicy
adiciona uma política de disjuntor. As solicitações externas adicionais são bloqueadas por 30 segundos quando ocorrem cinco tentativas com falha consecutivamente. As políticas de disjuntor são políticas com estado. Todas as chamadas por meio desse cliente compartilham o mesmo estado do circuito.
Adicionar políticas do registro do Polly
Uma abordagem para gerenciar as políticas usadas com frequência é defini-las uma única vez e registrá-las em um PolicyRegistry
. É fornecido um método de extensão que permite que um manipulador seja adicionado usando uma política do registro:
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regulartimeouthandler")
.AddPolicyHandlerFromRegistry("regular");
No código anterior, duas políticas são registradas quando PolicyRegistry
é adicionado ao ServiceCollection
. Para usar uma política do registro, o método AddPolicyHandlerFromRegistry
é usado, passando o nome da política a ser aplicada.
Mais informações sobre o IHttpClientFactory
e as integrações do Polly podem ser encontradas no Wiki do Polly.
HttpClient e gerenciamento de tempo de vida
Uma nova instância de HttpClient
é chamada, sempre que CreateClient
é chamado na IHttpClientFactory
. Há um HttpMessageHandler por cliente nomeado. O alocador gerencia a vida útil das instâncias do HttpMessageHandler
.
IHttpClientFactory
cria pools com as instâncias de HttpMessageHandler
criadas pelo alocador para reduzir o consumo de recursos. Uma instância de HttpMessageHandler
poderá ser reutilizada no pool, ao criar uma nova instância de HttpClient
, se o respectivo tempo de vida não tiver expirado.
O pooling de manipuladores é preferível porque normalmente cada manipulador gerencia as próprias conexões HTTP subjacentes. Criar mais manipuladores do que o necessário pode resultar 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. O valor padrão pode ser substituído para cada cliente nomeado. Para substituí-lo, chame SetHandlerLifetime no IHttpClientBuilder
que é retornado ao criar o cliente:
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Não é necessário descartar o cliente. O descarte cancela as solicitações de saída e garante que a determinada instância de HttpClient
não seja usada depois de chamar Dispose. IHttpClientFactory
rastreia e descarta recursos usados pelas instâncias de HttpClient
. Geralmente, as instâncias de HttpClient
podem ser tratadas como objetos do .NET e não exigem descarte.
Manter uma única instância de HttpClient
ativa por uma longa duração é um padrão comum usado, antes do início de IHttpClientFactory
. Esse padrão se torna desnecessário após a migração para IHttpClientFactory
.
Alternativas ao IHttpClientFactory
O uso de IHttpClientFactory
em um aplicativo habilitado para DI evita:
- Problemas de esgotamento de recursos ao agrupar instâncias
HttpMessageHandler
. - Problemas de DNS obsoletos com a reciclagem de instâncias
HttpMessageHandler
em intervalos regulares.
Há maneiras alternativas de resolver os problemas anteriores usando uma instância de SocketsHttpHandler de longa duração.
- Crie uma instância de
SocketsHttpHandler
quando o aplicativo iniciar e use-a na vida útil do aplicativo. - Configure PooledConnectionLifetime com um valor apropriado com base nos tempos de atualização de DNS.
- Crie instâncias
HttpClient
usandonew HttpClient(handler, disposeHandler: false)
conforme a necessidade.
As abordagens anteriores resolvem os problemas de gerenciamento de recursos que IHttpClientFactory
resolve de maneira semelhante.
- O
SocketsHttpHandler
compartilha conexões entre instâncias deHttpClient
. Esse compartilhamento impede o esgotamento do soquete. - O
SocketsHttpHandler
recicla conexões de acordo comPooledConnectionLifetime
para evitar problemas de DNS obsoletos.
Cookies
As instâncias de HttpMessageHandler
em pool resultam no compartilhamento de CookieContainer
. O compartilhamento do objeto CookieContainer
de forma inesperada geralmente resulta em código incorreto. Para aplicativos que exigem cookies, considere:
- Desabilitar o tratamento automático de cookie
- Evitar
IHttpClientFactory
Chamar ConfigurePrimaryHttpMessageHandler para desabilitar o tratamento automático de cookie:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Log
Os clientes criado pelo IHttpClientFactory
registram mensagens de log para todas as solicitações. Habilite o nível apropriado de informações na configuração de log para ver as mensagens de log padrão. Os registros em log adicionais, como o registro em log dos cabeçalhos de solicitação, estão incluídos somente no nível de rastreamento.
A categoria de log usada para cada cliente inclui o nome do cliente. Um cliente chamado MyNamedClient, por exemplo, registra mensagens com uma categoria de System.Net.Http.HttpClient.MyNamedClient.LogicalHandler
. Mensagens sufixadas com LogicalHandler ocorrem fora do pipeline do manipulador de solicitações. Na solicitação, as mensagens são registradas em log antes que qualquer outro manipulador no pipeline a tenha processado. Na resposta, as mensagens são registradas depois que qualquer outro manipulador do pipeline tenha recebido a resposta.
O registro em log também ocorre dentro do pipeline do manipulador de solicitações. No caso do exemplo de MyNamedClient, essas mensagens são registradas em log na categoria de log System.Net.Http.HttpClient.MyNamedClient.ClientHandler
. Para a solicitação, isso ocorre depois que todos os outros manipuladores são executados e imediatamente antes que a solicitação seja enviada pela rede. Na resposta, esse registro em log inclui o estado da resposta antes que ela seja retornada por meio do pipeline do manipulador.
Habilitar o registro em log dentro e fora do pipeline permite a inspeção das alterações feitas por outros manipuladores do pipeline. Isso pode incluir, por exemplo, alterações nos cabeçalhos de solicitação ou no código de status da resposta.
Incluir o nome do cliente na categoria de log permite a filtragem de log para clientes nomeados específicos, quando necessário.
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. O representante que é usado para criar e configurar o HttpMessageHandler
primário usado pelo cliente:
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
Usar IHttpClientFactory em um aplicativo de console
Em um aplicativo de console, adicione as seguintes referências de pacote ao projeto:
No exemplo a seguir:
- IHttpClientFactory é registrado no contêiner de serviço do Host genérico.
MyService
cria uma instância de fábrica do cliente do serviço, que é usada para criar umHttpClient
.HttpClient
é usado para recuperar uma página da Web.- O método
GetPage
do serviço é executado para gravar os primeiros 500 caracteres do conteúdo da página da Web no console. Para obter mais informações sobre serviços de chamada deProgram.Main
, confira Injeção de dependência no ASP.NET Core.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware de propagação de cabeçalho
A propagação de cabeçalho é um middleware com suporte da comunidade que propaga cabeçalhos HTTP da solicitação de entrada para as solicitações de cliente HTTP de saída. Para usar a propagação de cabeçalho:
Referencie a porta com suporte da comunidade do pacote HeaderPropagation. O ASP.NET Core 3.1 e posterior dá suporte a microsoft.AspNetCore.HeaderPropagation.
Configure o middleware e
HttpClient
emStartup
:public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseMvc(); }
O cliente inclui os cabeçalhos configurados em solicitações de saída:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);