Fazer solicitações HTTP usando IHttpClientFactory no ASP.NET Core

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 em HttpClient.
  • 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 vida HttpClient.
  • Adiciona uma experiência de registro em log configurável (via ILogger) para todas as solicitações enviadas por meio de clientes criados pelo alocador.

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 HttpClients 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:

  1. Crie uma instância de HttpClient.
  2. Crie uma instância de GitHubService, passando a instância de HttpClient 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 usando System.Text.Json.
  • Cria uma instância de StringContent para empacotar o JSON serializado para enviar o corpo da solicitação HTTP.
  • Chama PostAsync a fim de 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:

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:

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 e Long, 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 usando new 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 de HttpClient. Esse compartilhamento impede o esgotamento do soquete.
  • O SocketsHttpHandler recicla conexões de acordo com PooledConnectionLifetime 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 de IHttpClientFactory.
  • GitHubService usa IHttpClientFactory para criar uma instância de HttpClient, 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 em Program.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

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 em HttpClient.
  • 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 vida HttpClient.
  • Adiciona uma experiência de registro em log configurável (via ILogger) para todas as solicitações enviadas por meio de clientes criados pelo alocador.

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 HttpClients 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:

  1. Crie uma instância de HttpClient.
  2. Crie uma instância de GitHubService, passando a instância de HttpClient 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 usando System.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 a fim de 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:

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:

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 usando new 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 de HttpClient. Esse compartilhamento impede o esgotamento do soquete.
  • O SocketsHttpHandler recicla conexões de acordo com PooledConnectionLifetime 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 um HttpClient. HttpClient é usado para recuperar uma página da Web.
  • Main cria um escopo para executar o método GetPage 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 em Startup:

    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 em HttpClient.
  • 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 vida HttpClient.
  • Adiciona uma experiência de registro em log configurável (via ILogger) para todas as solicitações enviadas por meio de clientes criados pelo alocador.

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 HttpClients 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:

  1. Crie uma instância de HttpClient.
  2. Crie uma instância de GitHubService, passando a instância de HttpClient 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 usando System.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 a fim de 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:

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:

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 usando new 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 de HttpClient. Esse compartilhamento impede o esgotamento do soquete.
  • O SocketsHttpHandler recicla conexões de acordo com PooledConnectionLifetime 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 um HttpClient. HttpClient é usado para recuperar uma página da Web.
  • Main cria um escopo para executar o método GetPage 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 em Startup:

    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 de HttpClient.
  • Adiciona uma experiência de registro em log configurável (via ILogger) para todas as solicitações enviadas por meio de clientes criados pelo alocador.

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 usando new 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 de HttpClient. Esse compartilhamento impede o esgotamento do soquete.
  • O SocketsHttpHandler recicla conexões de acordo com PooledConnectionLifetime 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 um HttpClient. 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 de Program.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 em Startup:

    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(...);
    

Recursos adicionais