Partilhar via


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

Note

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.

Warning

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Por Kirk Larkin, Steve Gordon, Glenn Condron e Ryan Nowak.

Um IHttpClientFactory pode ser registrado e usado para configurar e criar HttpClient instâncias em um aplicativo. IHttpClientFactory oferece os seguintes benefícios:

  • Fornece um local central para nomear e configurar instâncias lógicas HttpClient . Por exemplo, um cliente chamado github pode ser registrado e configurado para acessar o GitHub. Um cliente padrão pode ser registrado para acesso geral.
  • Codifica o conceito de middleware de saída através da delegação de manipuladores em HttpClient. Fornece extensões para middleware baseado em Polly para aproveitar a delegação de manipuladores no HttpClient.
  • Gerencia o pool e o tempo de vida das instâncias subjacentes HttpClientMessageHandler . A gestão automática evita problemas comuns de DNS (Sistema de Nomes de Domínio) que ocorrem quando se gerem manualmente os períodos de HttpClient vida.
  • Adiciona uma experiência de registro configurável (via ILogger) para todas as solicitações enviadas através de clientes criados pela fábrica.

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 para selecionar uma versão 2.x deste tópico.

Padrões de consumo

Existem várias maneiras como IHttpClientFactory pode ser usado numa aplicação.

A melhor abordagem depende dos requisitos do aplicativo.

Utilização básica

Registe IHttpClientFactory chamando AddHttpClient em Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddHttpClient();

Um IHttpClientFactory pode ser solicitado usando injeção de dependência (DI). O código a seguir usa IHttpClientFactory para criar uma HttpClient instância:

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 tem impacto na forma como HttpClient é utilizado. Em locais onde instâncias de HttpClient são criadas numa aplicação existente, substitua essas ocorrências por chamadas a CreateClient.

Clientes nomeados

Clientes nomeados são uma boa escolha quando:

  • O aplicativo requer muitos usos distintos do HttpClient.
  • Muitos HttpClients têm configurações diferentes.

Especifique a configuração de um nome HttpClient durante seu registro em 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 https://api.github.com/.
  • Dois cabeçalhos necessários para trabalhar com a API do GitHub.

CreateClient

Cada vez que CreateClient é chamado:

  • Uma nova instância do HttpClient é criada.
  • Chama-se a ação de configuração.

Para criar um cliente nomeado, passe seu nome para CreateClient:

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 de host. O código pode passar apenas o caminho, uma vez que o endereço base configurado para o cliente é usado.

Clientes digitados

Clientes digitados:

  • Forneça os mesmos recursos que os clientes nomeados sem a necessidade de usar cadeias de caracteres como chaves.
  • Fornece IntelliSense e ajuda do compilador ao consumir clientes.
  • Forneça um único local para configurar e interagir com um determinado HttpClient. Por exemplo, um único cliente tipificado pode ser usado:
    • Para um único endpoint de backend.
    • Para encapsular toda a lógica que lida com o endpoint.
  • Trabalhe com DI e pode ser injetado quando necessário no aplicativo.

Um cliente tipado aceita um HttpClient parâmetro no seu construtor:

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 é integrada no cliente tipado.
  • A instância fornecida HttpClient é armazenada como um campo privado.

Métodos específicos de API podem ser criados para expor HttpClient funcionalidade. Por exemplo, o método GetAspNetCoreDocsBranches encapsula o código para recuperar ramificações de GitHub dos documentos.

O seguinte código chama AddHttpClient em Program.cs para registar a classe de cliente tipado GitHubService:

builder.Services.AddHttpClient<GitHubService>();

O cliente digitado é registrado como transitório com DI. No código anterior, AddHttpClient registra-se GitHubService como um serviço transitório. Este registo utiliza 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 o seu construtor.

O cliente digitado 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)
        {
            // ...
        }
    }
}

A configuração para um cliente tipado também pode ser especificada durante o seu registo em Program.cs, em vez de no construtor do cliente tipado.

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 o Refit. Refit é uma REST biblioteca para .NET. Ele converte REST APIs em interfaces interativas. Chamada 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 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 suporta outros verbos HTTP, incluindo:

  • POST
  • PUT
  • DELETE
  • PATCH

Para obter uma lista completa dos verbos HTTP suportados, consulte HttpMethod.

O exemplo a seguir 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 TodoItem parâmetro para JSON usando System.Text.Json.
  • Cria uma instância de StringContent para empacotar o JSON serializado para enviar no corpo da solicitação HTTP.
  • Chamadas PostAsync para enviar o conteúdo JSON para a URL especificada. Esta é uma URL relativa que é adicionada ao HttpClient.BaseAddress.
  • Chama EnsureSuccessStatusCode para lançar uma exceção se o código de status da resposta não indicar sucesso.

HttpClient também suporta outros tipos de conteúdo. Por exemplo, MultipartContent e StreamContent. Para obter uma lista completa do conteúdo suportado, consulte HttpContent.

O exemplo a seguir mostra uma solicitação HTTP PUT:

public async Task 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 SaveItemAsync método chama PutAsync em vez de PostAsync.

O exemplo a seguir 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 DeleteItemAsync método chama DeleteAsync. Como as solicitações HTTP DELETE normalmente não contêm corpo, o DeleteAsync método não fornece uma sobrecarga que aceita uma instância de HttpContent.

Para obter mais informações sobre a utilização de diferentes verbos HTTP com HttpClient, consulte HttpClient.

Middleware de solicitação de saída

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 para cada cliente nomeado.
  • Suporta registo e encadeamento de vários manipuladores para criar um pipeline de middleware para pedidos de saída. Cada um desses manipuladores é capaz de executar o trabalho antes e depois da solicitação de saída. Este padrão:
    • É semelhante ao pipeline de middleware de entrada no ASP.NET Core.
    • Fornece um mecanismo para gerenciar preocupações transversais em torno de solicitações HTTP, como:
      • colocação em cache
      • processamento de erros
      • serialização
      • registo

Para criar um handler de delegação:

  • Derivar de DelegatingHandler.
  • Anule 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 X-API-KEY cabeçalho está na solicitação. Se X-API-KEY estiver faltando, BadRequest é devolvido.

Mais de um manipulador pode ser adicionado à configuração para um HttpClient com Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:

builder.Services.AddTransient<ValidateHeaderHandler>();

builder.Services.AddHttpClient("HttpMessageHandler")
    .AddHttpMessageHandler<ValidateHeaderHandler>();

No código anterior, ValidateHeaderHandler é registado com DI. Uma vez registrado, AddHttpMessageHandler pode ser chamado, passando o tipo para o manipulador.

Vários manipuladores podem ser registrados na ordem em que devem ser executados. Cada manipulador encapsula o próximo manipulador até que o final HttpClientHandler 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 novo manipulador delegante, ele usa DI para cumprir os parâmetros do construtor do manipulador. IHttpClientFactory cria um escopo de DI separado para cada handler, o que pode levar a um comportamento surpreendente quando um handler consome um serviço definido por escopo.

Por exemplo, considere a seguinte interface e sua implementação, que representa uma tarefa como uma operação com um identificador, OperationId:

public interface IOperationScoped
{
    string OperationId { get; }
}

public class OperationScoped : IOperationScoped
{
    public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}

Como o próprio nome sugere, IOperationScoped é registado com DI usando um tempo de vida útil definido por um escopo:

builder.Services.AddScoped<IOperationScoped, OperationScoped>();

O seguinte manipulador de delegação consome e usa IOperationScoped para definir o cabeçalho X-OPERATION-ID na 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 HttpRequestsSample download, 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 só muda 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 manipuladores de mensagens:

Usar manipuladores baseados em Polly

IHttpClientFactory integra-se com a biblioteca de terceiros Polly. Polly é uma biblioteca abrangente de resiliência e tratamento de falhas transitórias para .NET. Ele permite que os desenvolvedores expressem políticas como Retry, Circuit Breaker, Timeout, Bulkhead Isolation e Fallback de forma fluente e segura para threads.

Estão disponíveis métodos de extensão para habilitar o uso de políticas Polly com instâncias configuradas HttpClient. As extensões Polly suportam a adição de manipuladores baseados em Polly aos clientes. Polly requer o pacote NuGet Microsoft.Extensions.Http.Polly .

Gerir falhas transitórias

As falhas normalmente ocorrem quando as chamadas HTTP externas são transitórias. AddTransientHttpErrorPolicy permite definir uma política para lidar com erros transitórios. As políticas configuradas com AddTransientHttpErrorPolicy manipulam as seguintes respostas:

AddTransientHttpErrorPolicy fornece acesso a um PolicyBuilder objeto configurado para lidar com 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 WaitAndRetryAsync política é 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 AddPolicyHandler sobrecarga inspeciona a solicitação para decidir qual política deve ser aplicada:

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 for um HTTP GET, um tempo limite de 10 segundos será aplicado. Para qualquer outro método HTTP, um tempo limite de 30 segundos é usado.

Adicionar vários manipuladores 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 nova tentativa. As solicitações com falha são repetidas até três vezes.
  • A segunda AddTransientHttpErrorPolicy chamada adiciona uma política de disjuntor. Outras solicitações externas são bloqueadas por 30 segundos se 5 tentativas falhadas ocorrerem sequencialmente. As políticas de disjuntor têm monitoração de estado. Todas as chamadas através deste cliente partilham o mesmo estado de circuito.

Adicionar políticas do registro Polly

Uma abordagem para gerir políticas regularmente usadas é defini-las uma vez e registá-las com 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 registo Polly.
  • AddPolicyHandlerFromRegistry configura clientes nomeados individuais para usar essas políticas do registro Polly.

Para obter mais informações sobre IHttpClientFactory e integrações do Polly, consulte o wiki do Polly.

HttpClient e gestão do ciclo de vida

Uma nova HttpClient instância é retornada sempre que CreateClient é chamada no IHttpClientFactory. Um HttpMessageHandler é criado para cliente nomeado. A fábrica gerencia o tempo de vida das HttpMessageHandler instâncias.

IHttpClientFactory agrupa as HttpMessageHandler instâncias criadas pela fábrica para reduzir o consumo de recursos. Uma HttpMessageHandler instância pode ser reutilizada do pool ao criar uma nova HttpClient instância se seu tempo de vida não tiver expirado.

O pool de manipuladores é desejável, pois cada manipulador normalmente gerencia suas 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 às alterações de DNS (Sistema de Nomes de Domínio).

O tempo de vida padrão do manipulador é de dois minutos. O valor padrão pode ser substituído individualmente para cada cliente nomeado.

builder.Services.AddHttpClient("HandlerLifetime")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

HttpClient as instâncias geralmente podem ser tratadas como objetos .NET que não exigem eliminação. O descarte cancela as solicitações de saída e garante que a instância dada HttpClient não possa ser usada após a chamada Dispose. IHttpClientFactory rastreia e elimina os recursos usados por instâncias de HttpClient.

Manter uma única HttpClient instância ativa por um longo período é um padrão comum usado antes do início do IHttpClientFactory. Esse padrão se torna desnecessário após a migração para o IHttpClientFactory.

Alternativas a IHttpClientFactory

Usar IHttpClientFactory em um aplicativo habilitado para DI evita:

  • Problemas de esgotamento de recursos devido ao agrupamento de instâncias HttpMessageHandler.
  • Problemas de DNS desatualizados ao alternar entre HttpMessageHandler instâncias em intervalos regulares.

Existem maneiras alternativas de resolver os problemas anteriores usando uma instância duradoura SocketsHttpHandler.

  • Crie uma instância de SocketsHttpHandler quando o aplicativo é iniciado e use-a durante a vida útil do aplicativo.
  • Configure PooledConnectionLifetime para um valor apropriado com base nos tempos de atualização do DNS.
  • Crie HttpClient instâncias usando new HttpClient(handler, disposeHandler: false) conforme necessário.

As abordagens anteriores resolvem os problemas de gestão de recursos que IHttpClientFactory resolve de forma semelhante.

  • O SocketsHttpHandler compartilha conexões entre HttpClient instâncias. Esta partilha evita o esgotamento do socket.
  • O SocketsHttpHandler ciclos de conexões de acordo com PooledConnectionLifetime para evitar problemas com DNS desatualizado.

Logging

Clientes criados através de IHttpClientFactory registam mensagens de log para todos os pedidos. Habilite o nível de informações apropriado na configuração de log para ver as mensagens de log padrão. O registo adicional, como o registo de cabeçalhos de solicitação, só é incluído no nível de traça.

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". As mensagens sufixadas com LogicalHandler ocorrem fora do pipeline do manipulador de solicitações. Na solicitação, as mensagens são registradas antes que qualquer outro manipulador no pipeline as tenha processado. Na resposta, as mensagens são registadas após todos os outros gestores de pipeline terem recebido a resposta.

O registro em log também ocorre dentro do fluxo de processamento 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 foram executados e imediatamente antes que a solicitação seja enviada. Na resposta, esse log inclui o estado da resposta antes que ela passe de volta pelo pipeline do manipulador.

Habilitar o registo dentro e fora do pipeline permite a inspeção das alterações feitas pelos outros manipuladores de pipeline. Isso pode incluir alterações nos cabeçalhos das solicitações ou no código de status da resposta.

A inclusão do nome do cliente na categoria de log permite a filtragem de log para clientes nomeados específicos.

Configurar o HttpMessageHandler

Pode ser necessário controlar a configuração interna do HttpMessageHandler utilizado por um cliente.

Um IHttpClientBuilder é retornado ao adicionar clientes nomeados ou digitados. O ConfigurePrimaryHttpMessageHandler método de extensão pode ser usado para definir um delegado. O delegado é usado para criar e configurar o componente principal HttpMessageHandler usado por aquele cliente.

builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
    .ConfigurePrimaryHttpMessageHandler(() =>
        new HttpClientHandler
        {
            AllowAutoRedirect = true,
            UseDefaultCredentials = true
        });

Cookies

As instâncias agrupadas HttpMessageHandler resultam em CookieContainer objetos sendo compartilhados. O compartilhamento imprevisto CookieContainer de objetos geralmente resulta em código incorreto. Para aplicações que requerem cookies, considere:

  • Desativar o tratamento automático cookie
  • Evitar IHttpClientFactory

Chamada ConfigurePrimaryHttpMessageHandler para desativar o tratamento automático 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 estão registrados no contêiner de serviço do Host Genérico .
  • GitHubService é solicitado à DI, que por sua vez solicita uma instância de IHttpClientFactory.
  • GitHubService usa IHttpClientFactory para criar uma instância de HttpClient, que utiliza para recuperar ramificações de documentos do GitHub.
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çalhos é um middleware ASP.NET Core para propagar cabeçalhos HTTP da solicitação de entrada para as solicitações de saída HttpClient. 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 configurada HttpClient , que inclui os cabeçalhos adicionados.

Recursos adicionais

Por Kirk Larkin, Steve Gordon, Glenn Condron e Ryan Nowak.

Um IHttpClientFactory pode ser registrado e usado para configurar e criar HttpClient instâncias em um aplicativo. IHttpClientFactory oferece os seguintes benefícios:

  • Fornece um local central para nomear e configurar instâncias lógicas HttpClient . Por exemplo, um cliente chamado github pode ser registrado e configurado para acessar o GitHub. Um cliente padrão pode ser registrado para acesso geral.
  • Codifica o conceito de middleware de saída através da delegação de manipuladores em HttpClient. Fornece extensões para middleware baseado em Polly para aproveitar a delegação de manipuladores no HttpClient.
  • Gerencia o pool e o tempo de vida das instâncias subjacentes HttpClientMessageHandler . A gestão automática evita problemas comuns de DNS (Sistema de Nomes de Domínio) que ocorrem quando se gerem manualmente os períodos de HttpClient vida.
  • Adiciona uma experiência de registro configurável (via ILogger) para todas as solicitações enviadas através de clientes criados pela fábrica.

Visualize ou baixe o código de exemplo (como fazer o download).

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 para selecionar uma versão 2.x deste tópico.

Padrões de consumo

Existem várias maneiras como IHttpClientFactory pode ser usado numa aplicação.

A melhor abordagem depende dos requisitos do aplicativo.

Utilização básica

IHttpClientFactory pode ser registado ao chamar 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 usando injeção de dependência (DI). O código a seguir usa IHttpClientFactory para criar uma HttpClient instância:

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 tem impacto na forma como HttpClient é utilizado. Em locais onde instâncias de HttpClient são criadas numa aplicação existente, substitua essas ocorrências por chamadas a CreateClient.

Clientes nomeados

Clientes nomeados são uma boa escolha quando:

  • O aplicativo requer muitos usos distintos do HttpClient.
  • Muitos HttpClients têm configurações diferentes.

A configuração de um nome HttpClient 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 https://api.github.com/.
  • Dois cabeçalhos necessários para trabalhar com a API do GitHub.

CreateClient

Cada vez que CreateClient é chamado:

  • Uma nova instância do HttpClient é criada.
  • Chama-se a ação de configuração.

Para criar um cliente nomeado, passe seu nome para CreateClient:

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 de host. O código pode passar apenas o caminho, uma vez que o endereço base configurado para o cliente é usado.

Clientes digitados

Clientes digitados:

  • Forneça os mesmos recursos que os clientes nomeados sem a necessidade de usar cadeias de caracteres como chaves.
  • Fornece IntelliSense e ajuda do compilador ao consumir clientes.
  • Forneça um único local para configurar e interagir com um determinado HttpClient. Por exemplo, um único cliente tipificado pode ser usado:
    • Para um único endpoint de backend.
    • Para encapsular toda a lógica que lida com o endpoint.
  • Trabalhe com DI e pode ser injetado quando necessário no aplicativo.

Um cliente tipado aceita um HttpClient parâmetro no seu construtor:

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 é integrada no cliente tipado.
  • O HttpClient objeto é exposto como uma propriedade pública.

Métodos específicos de API podem ser criados para expor HttpClient funcionalidade. Por exemplo, o GetAspNetDocsIssues método encapsula o código para recuperar problemas abertos.

O código a seguir chama AddHttpClient em Startup.ConfigureServices para registrar uma classe de cliente tipada:

services.AddHttpClient<GitHubService>();

O cliente digitado é registrado como transitório com DI. No código anterior, AddHttpClient registra-se GitHubService como um serviço transitório. Este registo utiliza 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 o seu construtor.

O cliente digitado 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 para um cliente tipado pode ser especificada durante o registo em Startup.ConfigureServices, em vez de no construtor do cliente tipado.

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 num cliente tipado. Em vez de expô-lo como uma propriedade, defina um método que chame a HttpClient instância 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. Acesso ao HttpClient é pelo método público GetRepos .

Clientes gerados

IHttpClientFactory pode ser usado em combinação com bibliotecas de terceiros, como o Refit. Refit é uma REST biblioteca para .NET. Ele converte REST APIs em interfaces interativas. Uma implementação da interface é gerada dinamicamente pelo RestService, usando HttpClient para fazer as chamadas 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 tipado pode ser adicionado, usando o 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 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 suporta outros verbos HTTP, incluindo:

  • POST
  • PUT
  • DELETE
  • PATCH

Para obter uma lista completa dos verbos HTTP suportados, consulte HttpMethod.

O exemplo a seguir 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 TodoItem parâmetro para JSON usando System.Text.Json. Isto usa uma instância de JsonSerializerOptions para configurar o processo de serialização.
  • Cria uma instância de StringContent para empacotar o JSON serializado para enviar no corpo da solicitação HTTP.
  • Chamadas PostAsync para enviar o conteúdo JSON para a URL especificada. Esta é uma URL relativa que é adicionada ao HttpClient.BaseAddress.
  • Chama EnsureSuccessStatusCode para lançar uma exceção se o código de status da resposta não indicar sucesso.

HttpClient também suporta outros tipos de conteúdo. Por exemplo, MultipartContent e StreamContent. Para obter uma lista completa do conteúdo suportado, consulte HttpContent.

O exemplo a seguir mostra uma solicitação HTTP PUT:

public async Task 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 SaveItemAsync método chama PutAsync em vez de PostAsync.

O exemplo a seguir 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 DeleteItemAsync método chama DeleteAsync. Como as solicitações HTTP DELETE normalmente não contêm corpo, o DeleteAsync método não fornece uma sobrecarga que aceita uma instância de HttpContent.

Para obter mais informações sobre a utilização de diferentes verbos HTTP com HttpClient, consulte HttpClient.

Middleware de solicitação de saída

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 para cada cliente nomeado.
  • Suporta registo e encadeamento de vários manipuladores para criar um pipeline de middleware para pedidos de saída. Cada um desses manipuladores é capaz de executar o trabalho antes e depois da solicitação de saída. Este padrão:
    • É semelhante ao pipeline de middleware de entrada no ASP.NET Core.
    • Fornece um mecanismo para gerenciar preocupações transversais em torno de solicitações HTTP, como:
      • colocação em cache
      • processamento de erros
      • serialização
      • registo

Para criar um handler de delegação:

  • Derivar de DelegatingHandler.
  • Anule 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 X-API-KEY cabeçalho está na solicitação. Se X-API-KEY estiver faltando, BadRequest é devolvido.

Mais de um manipulador pode ser adicionado à configuração para 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, ValidateHeaderHandler é registado com DI. Uma vez registrado, AddHttpMessageHandler pode ser chamado, passando o tipo para o manipulador.

Vários manipuladores podem ser registrados na ordem em que devem ser executados. Cada manipulador encapsula o próximo manipulador até que o final HttpClientHandler 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 novo manipulador delegante, ele usa DI para cumprir os parâmetros do construtor do manipulador. IHttpClientFactory cria um escopo de DI separado para cada handler, o que pode levar a um comportamento surpreendente quando um handler consome um serviço definido por escopo.

Por exemplo, considere a seguinte interface e sua implementação, que representa uma tarefa como uma operação com um identificador, OperationId:

public interface IOperationScoped 
{
    string OperationId { get; }
}

public class OperationScoped : IOperationScoped
{
    public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}

Como o próprio nome sugere, IOperationScoped é registado com DI usando um tempo de vida útil definido por um 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 para definir o cabeçalho X-OPERATION-ID na 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 HttpRequestsSample download, navegue até /Operation e recarregue a página. O valor do escopo da solicitação é alterado para cada solicitação, mas o valor do escopo do manipulador só muda 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 manipuladores de mensagens:

Usar manipuladores baseados em Polly

IHttpClientFactory integra-se com a biblioteca de terceiros Polly. Polly é uma biblioteca abrangente de resiliência e tratamento de falhas transitórias para .NET. Ele permite que os desenvolvedores expressem políticas como Retry, Circuit Breaker, Timeout, Bulkhead Isolation e Fallback de forma fluente e segura para threads.

Estão disponíveis métodos de extensão para habilitar o uso de políticas Polly com instâncias configuradas HttpClient. As extensões Polly suportam a adição de manipuladores baseados em Polly aos clientes. Polly requer o pacote NuGet Microsoft.Extensions.Http.Polly .

Gerir falhas transitórias

As falhas normalmente ocorrem quando as chamadas HTTP externas são transitórias. AddTransientHttpErrorPolicy permite definir uma política para lidar com erros transitórios. As políticas configuradas com AddTransientHttpErrorPolicy manipulam as seguintes respostas:

AddTransientHttpErrorPolicy fornece acesso a um PolicyBuilder objeto configurado para lidar com 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 WaitAndRetryAsync política é 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 AddPolicyHandler sobrecarga inspeciona a solicitação para decidir qual política deve 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 for um HTTP GET, um tempo limite de 10 segundos será aplicado. Para qualquer outro método HTTP, um tempo limite de 30 segundos é usado.

Adicionar vários manipuladores 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 nova tentativa. As solicitações com falha são repetidas até três vezes.
  • A segunda AddTransientHttpErrorPolicy chamada adiciona uma política de disjuntor. Outras solicitações externas são bloqueadas por 30 segundos se 5 tentativas falhadas ocorrerem sequencialmente. As políticas de disjuntor têm monitoração de estado. Todas as chamadas através deste cliente partilham o mesmo estado de circuito.

Adicionar políticas do registro Polly

Uma abordagem para gerir políticas regularmente usadas é defini-las uma vez e registá-las com um PolicyRegistry.

No seguinte código:

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 IHttpClientFactory e integrações do Polly, consulte o wiki do Polly.

HttpClient e gestão do ciclo de vida

Uma nova HttpClient instância é retornada sempre que CreateClient é chamada no IHttpClientFactory. Um HttpMessageHandler é criado para cliente nomeado. A fábrica gerencia o tempo de vida das HttpMessageHandler instâncias.

IHttpClientFactory agrupa as HttpMessageHandler instâncias criadas pela fábrica para reduzir o consumo de recursos. Uma HttpMessageHandler instância pode ser reutilizada do pool ao criar uma nova HttpClient instância se seu tempo de vida não tiver expirado.

O pool de manipuladores é desejável, pois cada manipulador normalmente gerencia suas 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 às alterações de DNS (Sistema de Nomes de Domínio).

O tempo de vida padrão do manipulador é de dois minutos. O valor padrão pode ser substituído individualmente para cada cliente nomeado.

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient("extendedhandlerlifetime")
        .SetHandlerLifetime(TimeSpan.FromMinutes(5));

    // Remaining code deleted for brevity.

HttpClient as instâncias geralmente podem ser tratadas como objetos .NET que não exigem eliminação. O descarte cancela as solicitações de saída e garante que a instância dada HttpClient não possa ser usada após a chamada Dispose. IHttpClientFactory rastreia e elimina os recursos usados por instâncias de HttpClient.

Manter uma única HttpClient instância ativa por um longo período é um padrão comum usado antes do início do IHttpClientFactory. Esse padrão se torna desnecessário após a migração para o IHttpClientFactory.

Alternativas a IHttpClientFactory

Usar IHttpClientFactory em um aplicativo habilitado para DI evita:

  • Problemas de esgotamento de recursos devido ao agrupamento de instâncias HttpMessageHandler.
  • Problemas de DNS desatualizados ao alternar entre HttpMessageHandler instâncias em intervalos regulares.

Existem maneiras alternativas de resolver os problemas anteriores usando uma instância duradoura SocketsHttpHandler.

  • Crie uma instância de SocketsHttpHandler quando o aplicativo é iniciado e use-a durante a vida útil do aplicativo.
  • Configure PooledConnectionLifetime para um valor apropriado com base nos tempos de atualização do DNS.
  • Crie HttpClient instâncias usando new HttpClient(handler, disposeHandler: false) conforme necessário.

As abordagens anteriores resolvem os problemas de gestão de recursos que IHttpClientFactory resolve de forma semelhante.

  • O SocketsHttpHandler compartilha conexões entre HttpClient instâncias. Esta partilha evita o esgotamento do socket.
  • O SocketsHttpHandler ciclos de conexões de acordo com PooledConnectionLifetime para evitar problemas com DNS desatualizado.

Cookies

As instâncias agrupadas HttpMessageHandler resultam em CookieContainer objetos sendo compartilhados. O compartilhamento imprevisto CookieContainer de objetos geralmente resulta em código incorreto. Para aplicações que requerem cookies, considere:

  • Desativar o tratamento automático cookie
  • Evitar IHttpClientFactory

Chamada ConfigurePrimaryHttpMessageHandler para desativar o tratamento automático cookie :

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            UseCookies = false,
        };
    });

Logging

Clientes criados através de IHttpClientFactory registam mensagens de log para todos os pedidos. Habilite o nível de informações apropriado na configuração de log para ver as mensagens de log padrão. O registo adicional, como o registo de cabeçalhos de solicitação, só é incluído no nível de traça.

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". As mensagens sufixadas com LogicalHandler ocorrem fora do pipeline do manipulador de solicitações. Na solicitação, as mensagens são registradas antes que qualquer outro manipulador no pipeline as tenha processado. Na resposta, as mensagens são registadas após todos os outros gestores de pipeline terem recebido a resposta.

O registro em log também ocorre dentro do fluxo de processamento 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 foram executados e imediatamente antes que a solicitação seja enviada. Na resposta, esse log inclui o estado da resposta antes que ela passe de volta pelo pipeline do manipulador.

Habilitar o registo dentro e fora do pipeline permite a inspeção das alterações feitas pelos outros manipuladores de pipeline. Isso pode incluir alterações nos cabeçalhos das solicitações ou no código de status da resposta.

A inclusão do nome do cliente na categoria de log permite a filtragem de log para clientes nomeados específicos.

Configurar o HttpMessageHandler

Pode ser necessário controlar a configuração interna do HttpMessageHandler utilizado por um cliente.

Um IHttpClientBuilder é retornado ao adicionar clientes nomeados ou digitados. O ConfigurePrimaryHttpMessageHandler método de extensão pode ser usado para definir um delegado. O delegado é usado para criar e configurar o componente principal HttpMessageHandler usado por aquele 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 está registado no contenedor de serviço do Host Genérico.
  • MyService cria uma instância de fábrica de cliente a partir 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 do GetPage 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çalhos é um middleware do ASP.NET Core para propagar cabeçalhos HTTP da requisição de entrada para as requisições do Cliente HTTP de saída. Para usar a propagação de cabeçalho:

  • Consulte o 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.

Um IHttpClientFactory pode ser registrado e usado para configurar e criar HttpClient instâncias em um aplicativo. IHttpClientFactory oferece os seguintes benefícios:

  • Fornece um local central para nomear e configurar instâncias lógicas HttpClient . Por exemplo, um cliente chamado github pode ser registrado e configurado para acessar o GitHub. Um cliente padrão pode ser registrado para acesso geral.
  • Codifica o conceito de middleware de saída através da delegação de manipuladores em HttpClient. Fornece extensões para middleware baseado em Polly para aproveitar a delegação de manipuladores no HttpClient.
  • Gerencia o pool e o tempo de vida das instâncias subjacentes HttpClientMessageHandler . A gestão automática evita problemas comuns de DNS (Sistema de Nomes de Domínio) que ocorrem quando se gerem manualmente os períodos de HttpClient vida.
  • Adiciona uma experiência de registro configurável (via ILogger) para todas as solicitações enviadas através de clientes criados pela fábrica.

Visualize ou baixe o código de exemplo (como fazer o download).

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 para selecionar uma versão 2.x deste tópico.

Padrões de consumo

Existem várias maneiras como IHttpClientFactory pode ser usado numa aplicação.

A melhor abordagem depende dos requisitos do aplicativo.

Utilização básica

IHttpClientFactory pode ser registado ao chamar 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 usando injeção de dependência (DI). O código a seguir usa IHttpClientFactory para criar uma HttpClient instância:

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 tem impacto na forma como HttpClient é utilizado. Em locais onde instâncias de HttpClient são criadas numa aplicação existente, substitua essas ocorrências por chamadas a CreateClient.

Clientes nomeados

Clientes nomeados são uma boa escolha quando:

  • O aplicativo requer muitos usos distintos do HttpClient.
  • Muitos HttpClients têm configurações diferentes.

A configuração de um nome HttpClient 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 https://api.github.com/.
  • Dois cabeçalhos necessários para trabalhar com a API do GitHub.

CreateClient

Cada vez que CreateClient é chamado:

  • Uma nova instância do HttpClient é criada.
  • Chama-se a ação de configuração.

Para criar um cliente nomeado, passe seu nome para CreateClient:

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 de host. O código pode passar apenas o caminho, uma vez que o endereço base configurado para o cliente é usado.

Clientes digitados

Clientes digitados:

  • Forneça os mesmos recursos que os clientes nomeados sem a necessidade de usar cadeias de caracteres como chaves.
  • Fornece IntelliSense e ajuda do compilador ao consumir clientes.
  • Forneça um único local para configurar e interagir com um determinado HttpClient. Por exemplo, um único cliente tipificado pode ser usado:
    • Para um único endpoint de backend.
    • Para encapsular toda a lógica que lida com o endpoint.
  • Trabalhe com DI e pode ser injetado quando necessário no aplicativo.

Um cliente tipado aceita um HttpClient parâmetro no seu construtor:

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 quiser ver os comentários de código traduzidos para outros idiomas além do inglês, avise-nos nesta discussão do GitHub .

No código anterior:

  • A configuração é integrada no cliente tipado.
  • O HttpClient objeto é exposto como uma propriedade pública.

Métodos específicos de API podem ser criados para expor HttpClient funcionalidade. Por exemplo, o GetAspNetDocsIssues método encapsula o código para recuperar problemas abertos.

O código a seguir chama AddHttpClient em Startup.ConfigureServices para registrar uma classe de cliente tipada:

services.AddHttpClient<GitHubService>();

O cliente digitado é registrado como transitório com DI. No código anterior, AddHttpClient registra-se GitHubService como um serviço transitório. Este registo utiliza 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 o seu construtor.

O cliente digitado 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 para um cliente tipado pode ser especificada durante o registo em Startup.ConfigureServices, em vez de no construtor do cliente tipado.

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 num cliente tipado. Em vez de expô-lo como uma propriedade, defina um método que chame a HttpClient instância 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. Acesso ao HttpClient é pelo método público GetRepos .

Clientes gerados

IHttpClientFactory pode ser usado em combinação com bibliotecas de terceiros, como o Refit. Refit é uma REST biblioteca para .NET. Ele converte REST APIs em interfaces interativas. Uma implementação da interface é gerada dinamicamente pelo RestService, usando HttpClient para fazer as chamadas 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 tipado pode ser adicionado, usando o 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 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 suporta outros verbos HTTP, incluindo:

  • POST
  • PUT
  • DELETE
  • PATCH

Para obter uma lista completa dos verbos HTTP suportados, consulte HttpMethod.

O exemplo a seguir 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 TodoItem parâmetro para JSON usando System.Text.Json. Isto usa uma instância de JsonSerializerOptions para configurar o processo de serialização.
  • Cria uma instância de StringContent para empacotar o JSON serializado para enviar no corpo da solicitação HTTP.
  • Chamadas PostAsync para enviar o conteúdo JSON para a URL especificada. Esta é uma URL relativa que é adicionada ao HttpClient.BaseAddress.
  • Chama EnsureSuccessStatusCode para lançar uma exceção se o código de status da resposta não indicar sucesso.

HttpClient também suporta outros tipos de conteúdo. Por exemplo, MultipartContent e StreamContent. Para obter uma lista completa do conteúdo suportado, consulte HttpContent.

O exemplo a seguir mostra uma solicitação HTTP PUT:

public async Task 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 SaveItemAsync método chama PutAsync em vez de PostAsync.

O exemplo a seguir 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 DeleteItemAsync método chama DeleteAsync. Como as solicitações HTTP DELETE normalmente não contêm corpo, o DeleteAsync método não fornece uma sobrecarga que aceita uma instância de HttpContent.

Para obter mais informações sobre a utilização de diferentes verbos HTTP com HttpClient, consulte HttpClient.

Middleware de solicitação de saída

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 para cada cliente nomeado.
  • Suporta registo e encadeamento de vários manipuladores para criar um pipeline de middleware para pedidos de saída. Cada um desses manipuladores é capaz de executar o trabalho antes e depois da solicitação de saída. Este padrão:
    • É semelhante ao pipeline de middleware de entrada no ASP.NET Core.
    • Fornece um mecanismo para gerenciar preocupações transversais em torno de solicitações HTTP, como:
      • colocação em cache
      • processamento de erros
      • serialização
      • registo

Para criar um handler de delegação:

  • Derivar de DelegatingHandler.
  • Anule 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 X-API-KEY cabeçalho está na solicitação. Se X-API-KEY estiver faltando, BadRequest é devolvido.

Mais de um manipulador pode ser adicionado à configuração para 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, ValidateHeaderHandler é registado com DI. Uma vez registrado, AddHttpMessageHandler pode ser chamado, passando o tipo para o manipulador.

Vários manipuladores podem ser registrados na ordem em que devem ser executados. Cada manipulador encapsula o próximo manipulador até que o final HttpClientHandler 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 novo manipulador delegante, ele usa DI para cumprir os parâmetros do construtor do manipulador. IHttpClientFactory cria um escopo de DI separado para cada handler, o que pode levar a um comportamento surpreendente quando um handler consome um serviço definido por escopo.

Por exemplo, considere a seguinte interface e sua implementação, que representa uma tarefa como uma operação com um identificador, OperationId:

public interface IOperationScoped 
{
    string OperationId { get; }
}

public class OperationScoped : IOperationScoped
{
    public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}

Como o próprio nome sugere, IOperationScoped é registado com DI usando um tempo de vida útil definido por um 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 para definir o cabeçalho X-OPERATION-ID na 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 HttpRequestsSample download, navegue até /Operation e recarregue a página. O valor do escopo da solicitação é alterado para cada solicitação, mas o valor do escopo do manipulador só muda 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 manipuladores de mensagens:

Usar manipuladores baseados em Polly

IHttpClientFactory integra-se com a biblioteca de terceiros Polly. Polly é uma biblioteca abrangente de resiliência e tratamento de falhas transitórias para .NET. Ele permite que os desenvolvedores expressem políticas como Retry, Circuit Breaker, Timeout, Bulkhead Isolation e Fallback de forma fluente e segura para threads.

Estão disponíveis métodos de extensão para habilitar o uso de políticas Polly com instâncias configuradas HttpClient. As extensões Polly suportam a adição de manipuladores baseados em Polly aos clientes. Polly requer o pacote NuGet Microsoft.Extensions.Http.Polly .

Gerir falhas transitórias

As falhas normalmente ocorrem quando as chamadas HTTP externas são transitórias. AddTransientHttpErrorPolicy permite definir uma política para lidar com erros transitórios. As políticas configuradas com AddTransientHttpErrorPolicy manipulam as seguintes respostas:

AddTransientHttpErrorPolicy fornece acesso a um PolicyBuilder objeto configurado para lidar com 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 WaitAndRetryAsync política é 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 AddPolicyHandler sobrecarga inspeciona a solicitação para decidir qual política deve 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 for um HTTP GET, um tempo limite de 10 segundos será aplicado. Para qualquer outro método HTTP, um tempo limite de 30 segundos é usado.

Adicionar vários manipuladores 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 nova tentativa. As solicitações com falha são repetidas até três vezes.
  • A segunda AddTransientHttpErrorPolicy chamada adiciona uma política de disjuntor. Outras solicitações externas são bloqueadas por 30 segundos se 5 tentativas falhadas ocorrerem sequencialmente. As políticas de disjuntor têm monitoração de estado. Todas as chamadas através deste cliente partilham o mesmo estado de circuito.

Adicionar políticas do registro Polly

Uma abordagem para gerir políticas regularmente usadas é defini-las uma vez e registá-las com um PolicyRegistry.

No seguinte código:

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 IHttpClientFactory e integrações do Polly, consulte o wiki do Polly.

HttpClient e gestão do ciclo de vida

Uma nova HttpClient instância é retornada sempre que CreateClient é chamada no IHttpClientFactory. Um HttpMessageHandler é criado para cliente nomeado. A fábrica gerencia o tempo de vida das HttpMessageHandler instâncias.

IHttpClientFactory agrupa as HttpMessageHandler instâncias criadas pela fábrica para reduzir o consumo de recursos. Uma HttpMessageHandler instância pode ser reutilizada do pool ao criar uma nova HttpClient instância se seu tempo de vida não tiver expirado.

O pool de manipuladores é desejável, pois cada manipulador normalmente gerencia suas 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 às alterações de DNS (Sistema de Nomes de Domínio).

O tempo de vida padrão do manipulador é de dois minutos. O valor padrão pode ser substituído individualmente para cada cliente nomeado.

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient("extendedhandlerlifetime")
        .SetHandlerLifetime(TimeSpan.FromMinutes(5));

    // Remaining code deleted for brevity.

HttpClient as instâncias geralmente podem ser tratadas como objetos .NET que não exigem eliminação. O descarte cancela as solicitações de saída e garante que a instância dada HttpClient não possa ser usada após a chamada Dispose. IHttpClientFactory rastreia e elimina os recursos usados por instâncias de HttpClient.

Manter uma única HttpClient instância ativa por um longo período é um padrão comum usado antes do início do IHttpClientFactory. Esse padrão se torna desnecessário após a migração para o IHttpClientFactory.

Alternativas a IHttpClientFactory

Usar IHttpClientFactory em um aplicativo habilitado para DI evita:

  • Problemas de esgotamento de recursos devido ao agrupamento de instâncias HttpMessageHandler.
  • Problemas de DNS desatualizados ao alternar entre HttpMessageHandler instâncias em intervalos regulares.

Existem maneiras alternativas de resolver os problemas anteriores usando uma instância duradoura SocketsHttpHandler.

  • Crie uma instância de SocketsHttpHandler quando o aplicativo é iniciado e use-a durante a vida útil do aplicativo.
  • Configure PooledConnectionLifetime para um valor apropriado com base nos tempos de atualização do DNS.
  • Crie HttpClient instâncias usando new HttpClient(handler, disposeHandler: false) conforme necessário.

As abordagens anteriores resolvem os problemas de gestão de recursos que IHttpClientFactory resolve de forma semelhante.

  • O SocketsHttpHandler compartilha conexões entre HttpClient instâncias. Esta partilha evita o esgotamento do socket.
  • O SocketsHttpHandler ciclos de conexões de acordo com PooledConnectionLifetime para evitar problemas com DNS desatualizado.

Cookies

As instâncias agrupadas HttpMessageHandler resultam em CookieContainer objetos sendo compartilhados. O compartilhamento imprevisto CookieContainer de objetos geralmente resulta em código incorreto. Para aplicações que requerem cookies, considere:

  • Desativar o tratamento automático cookie
  • Evitar IHttpClientFactory

Chamada ConfigurePrimaryHttpMessageHandler para desativar o tratamento automático cookie :

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            UseCookies = false,
        };
    });

Logging

Clientes criados através de IHttpClientFactory registam mensagens de log para todos os pedidos. Habilite o nível de informações apropriado na configuração de log para ver as mensagens de log padrão. O registo adicional, como o registo de cabeçalhos de solicitação, só é incluído no nível de traça.

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". As mensagens sufixadas com LogicalHandler ocorrem fora do pipeline do manipulador de solicitações. Na solicitação, as mensagens são registradas antes que qualquer outro manipulador no pipeline as tenha processado. Na resposta, as mensagens são registadas após todos os outros gestores de pipeline terem recebido a resposta.

O registro em log também ocorre dentro do fluxo de processamento 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 foram executados e imediatamente antes que a solicitação seja enviada. Na resposta, esse log inclui o estado da resposta antes que ela passe de volta pelo pipeline do manipulador.

Habilitar o registo dentro e fora do pipeline permite a inspeção das alterações feitas pelos outros manipuladores de pipeline. Isso pode incluir alterações nos cabeçalhos das solicitações ou no código de status da resposta.

A inclusão do nome do cliente na categoria de log permite a filtragem de log para clientes nomeados específicos.

Configurar o HttpMessageHandler

Pode ser necessário controlar a configuração interna do HttpMessageHandler utilizado por um cliente.

Um IHttpClientBuilder é retornado ao adicionar clientes nomeados ou digitados. O ConfigurePrimaryHttpMessageHandler método de extensão pode ser usado para definir um delegado. O delegado é usado para criar e configurar o componente principal HttpMessageHandler usado por aquele 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 está registado no contenedor de serviço do Host Genérico.
  • MyService cria uma instância de fábrica de cliente a partir 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 do GetPage 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çalhos é um middleware do ASP.NET Core para propagar cabeçalhos HTTP da requisição de entrada para as requisições do Cliente HTTP de saída. Para usar a propagação de cabeçalho:

  • Consulte o 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

Um IHttpClientFactory pode ser registrado e usado para configurar e criar HttpClient instâncias em um aplicativo. Oferece os seguintes benefícios:

  • Fornece um local central para nomear e configurar instâncias lógicas HttpClient . Por exemplo, um cliente github pode ser registrado e configurado para acessar o GitHub. Um cliente padrão pode ser registrado para outros fins.
  • Codifica o conceito de middleware de saída através dos manipuladores delegados em HttpClient e fornece extensões para que o middleware baseado em Polly possa tirar proveito disso.
  • Gerencia o pool e o tempo de vida das instâncias subjacentes HttpClientMessageHandler para evitar problemas comuns de DNS que ocorrem ao gerenciar HttpClient manualmente os tempos de vida.
  • Adiciona uma experiência de registro configurável (via ILogger) para todas as solicitações enviadas através de clientes criados pela fábrica.

Visualizar ou descarregar amostra de código (como descarregar)

Prerequisites

Os projetos destinados ao .NET Framework exigem a instalação do pacote NuGet Microsoft.Extensions.Http . Os projetos destinados ao .NET Core e fazem referência ao metapacote Microsoft.AspNetCore.App já incluem o Microsoft.Extensions.Http pacote.

Padrões de consumo

Existem várias maneiras como IHttpClientFactory pode ser usado numa aplicação.

Nenhum deles é rigorosamente superior ao outro. A melhor abordagem depende das restrições do aplicativo.

Utilização básica

O IHttpClientFactory pode ser registado chamando o método de extensão AddHttpClient no IServiceCollection, dentro do método Startup.ConfigureServices.

services.AddHttpClient();

Uma vez registrado, o código pode aceitar um IHttpClientFactory serviço em qualquer lugar onde serviços podem ser injetados com injeção de dependência (DI). O IHttpClientFactory pode ser usado para criar uma HttpClient instância:

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 IHttpClientFactory dessa maneira é uma boa maneira de refatorar um aplicativo existente. Não tem qualquer impacto na forma como HttpClient é utilizado. Em locais onde instâncias de HttpClient são atualmente criadas, substitua essas ocorrências por uma chamada para CreateClient.

Clientes nomeados

Se um aplicativo exigir muitos usos distintos do HttpClient, cada um com uma configuração diferente, uma opção é usar clientes nomeados. A configuração para uma entidade nomeada HttpClient pode ser especificada durante o registo no 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. Este cliente tem alguma configuração padrão aplicada, ou seja, o endereço base e dois cabeçalhos necessários para trabalhar com a API do GitHub.

Sempre que 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 string 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 de host. Ele pode passar apenas o caminho, uma vez que o endereço base configurado para o cliente é usado.

Clientes digitados

Clientes digitados:

  • Forneça os mesmos recursos que os clientes nomeados sem a necessidade de usar cadeias de caracteres como chaves.
  • Fornece IntelliSense e ajuda do compilador ao consumir clientes.
  • Forneça um único local para configurar e interagir com um determinado HttpClient. Por exemplo, um único cliente tipificado pode ser usado para um único ponto de extremidade de back-end e encapsular toda a lógica associada a esse ponto de extremidade.
  • Trabalha com DI e pode ser injetado quando necessário na sua aplicação.

Um cliente tipado aceita um HttpClient parâmetro no 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 acima, a configuração é movida para o cliente tipificado. O HttpClient objeto é exposto como uma propriedade pública. É possível definir métodos específicos da API para expor a funcionalidade HttpClient. O GetAspNetDocsIssues método encapsula o código necessário para consultar e analisar os problemas abertos mais recentes de um repositório GitHub.

Para registrar um cliente tipado, o método de extensão genérica AddHttpClient pode ser usado em Startup.ConfigureServices, especificando a classe de cliente tipada:

services.AddHttpClient<GitHubService>();

O cliente digitado é registrado como transitório com DI. O cliente digitado 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 para um cliente tipado pode ser especificada durante o registo em Startup.ConfigureServices, em vez de no construtor do cliente tipado.

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 tipado. Em vez de expô-lo como uma propriedade, podem ser fornecidos métodos públicos que invocam internamente a instância HttpClient.

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 GetRepos método.

Clientes gerados

IHttpClientFactory pode ser usado em combinação com outras bibliotecas de terceiros, como o Refit. Refit é uma REST biblioteca para .NET. Ele converte REST APIs em interfaces interativas. Uma implementação da interface é gerada dinamicamente pelo RestService, usando HttpClient para fazer as chamadas 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 tipado pode ser adicionado, usando o 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 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

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 para cada cliente nomeado. Ele suporta o registro e o encadeamento de vários manipuladores para criar um pipeline de middleware de solicitação de saída. Cada um desses manipuladores é capaz de executar o trabalho antes e depois da solicitação de saída. Esse padrão é semelhante ao pipeline de middleware de entrada no ASP.NET Core. O padrão fornece um mecanismo para gerenciar preocupações transversais em torno de solicitações HTTP, incluindo cache, tratamento de erros, serialização e registro.

Para criar um manipulador, defina uma classe derivada de DelegatingHandler. Substituir o método SendAsync para executar 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 X-API-KEY cabeçalho foi incluído na solicitação. Se o cabeçalho estiver faltando, ele pode evitar a chamada HTTP e retornar uma resposta adequada.

Durante o registo, um ou mais manipuladores podem ser adicionados à configuração de um HttpClient. Esta tarefa é realizada através 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, ValidateHeaderHandler é registado com DI. O manipulador deve ser registado no DI como um serviço transitório, nunca delimitado. Se o manipulador estiver registrado como um serviço com escopo e todos os serviços dos quais o manipulador depende forem descartáveis:

  • Os serviços do manipulador podem ser descartados antes que ele saia do escopo.
  • Os serviços do manipulador desativados provocam a falha do manipulador.

Uma vez registrado, AddHttpMessageHandler pode ser chamado, passando o tipo de manipulador.

Vários manipuladores podem ser registrados na ordem em que devem ser executados. Cada manipulador encapsula o próximo manipulador até que o final HttpClientHandler 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 manipuladores de mensagens:

  • Passe dados para o manipulador usando HttpRequestMessage.Properties.
  • Use IHttpContextAccessor para acessar a solicitação atual.
  • Crie um objeto de armazenamento personalizado AsyncLocal para passar os dados.

Usar manipuladores baseados em Polly

IHttpClientFactory integra-se com uma biblioteca popular de terceiros chamada Polly. Polly é uma biblioteca abrangente de resiliência e tratamento de falhas transitórias para .NET. Ele permite que os desenvolvedores expressem políticas como Retry, Circuit Breaker, Timeout, Bulkhead Isolation e Fallback de forma fluente e segura para threads.

Estão disponíveis métodos de extensão para habilitar o uso de políticas Polly com instâncias configuradas HttpClient. As extensões Polly:

  • Suporte à integração de manipuladores baseados em Polly para os clientes.
  • Pode ser usado após a instalação do pacote Microsoft.Extensions.Http.Polly NuGet. O pacote não está incluído na estrutura compartilhada do ASP.NET Core.

Gerir 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 está incluído que permite que uma política seja definida para lidar com erros transitórios. As políticas configuradas com este método de extensão lidam com HttpRequestException, respostas HTTP 5xx e respostas HTTP 408.

A AddTransientHttpErrorPolicy extensão pode ser usada dentro de Startup.ConfigureServices. A extensão fornece acesso a um PolicyBuilder objeto configurado para lidar com 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 WaitAndRetryAsync política é 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 em Polly. Uma dessas extensões é AddPolicyHandlera , que tem várias sobrecargas. Uma sobrecarga permite que a solicitação seja inspecionada ao definir 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 for um HTTP GET, um tempo limite de 10 segundos será aplicado. Para qualquer outro método HTTP, um tempo limite de 30 segundos é usado.

Adicionar vários manipuladores Polly

É comum aninhar políticas Polly para fornecer funcionalidade aprimorada:

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 AddTransientHttpErrorPolicy extensão para adicionar uma política de novas tentativas. As solicitações com falha são repetidas até três vezes. A segunda chamada para AddTransientHttpErrorPolicy adiciona uma política de disjuntor. Outras solicitações externas são bloqueadas por 30 segundos se cinco tentativas falhadas ocorrerem sequencialmente. As políticas de disjuntor têm monitoração de estado. Todas as chamadas através deste cliente partilham o mesmo estado de circuito.

Adicionar políticas do registro Polly

Uma abordagem para gerir políticas regularmente usadas é defini-las uma vez e registá-las com 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 o PolicyRegistry é adicionado ao ServiceCollection. Para usar uma política do Registro, o AddPolicyHandlerFromRegistry método é usado, passando o nome da política a ser aplicada.

Mais informações sobre IHttpClientFactory e integrações Polly podem ser encontradas na wiki Polly.

HttpClient e gestão do ciclo de vida

Uma nova HttpClient instância é retornada sempre que CreateClient é chamada no IHttpClientFactory. Há um HttpMessageHandler por cliente nomeado. A fábrica gerencia o tempo de vida das HttpMessageHandler instâncias.

IHttpClientFactory agrupa as HttpMessageHandler instâncias criadas pela fábrica para reduzir o consumo de recursos. Uma HttpMessageHandler instância pode ser reutilizada do pool ao criar uma nova HttpClient instância se seu tempo de vida não tiver expirado.

O pool de manipuladores é desejável, pois cada manipulador normalmente gerencia suas 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 às alterações de DNS.

O tempo de vida padrão do manipulador é de dois 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));

A eliminação do cliente não é obrigatória. O descarte cancela as solicitações de saída e garante que a instância dada HttpClient não possa ser usada após a chamada Dispose. IHttpClientFactory rastreia e elimina os recursos usados por instâncias de HttpClient. As HttpClient instâncias geralmente podem ser tratadas como objetos .NET que não exigem eliminação.

Manter uma única HttpClient instância ativa por um longo período é um padrão comum usado antes do início do IHttpClientFactory. Esse padrão se torna desnecessário após a migração para o IHttpClientFactory.

Alternativas a IHttpClientFactory

Usar IHttpClientFactory em um aplicativo habilitado para DI evita:

  • Problemas de esgotamento de recursos devido ao agrupamento de instâncias HttpMessageHandler.
  • Problemas de DNS desatualizados ao alternar entre HttpMessageHandler instâncias em intervalos regulares.

Existem maneiras alternativas de resolver os problemas anteriores usando uma instância duradoura SocketsHttpHandler.

  • Crie uma instância de SocketsHttpHandler quando o aplicativo é iniciado e use-a durante a vida útil do aplicativo.
  • Configure PooledConnectionLifetime para um valor apropriado com base nos tempos de atualização do DNS.
  • Crie HttpClient instâncias usando new HttpClient(handler, disposeHandler: false) conforme necessário.

As abordagens anteriores resolvem os problemas de gestão de recursos que IHttpClientFactory resolve de forma semelhante.

  • O SocketsHttpHandler compartilha conexões entre HttpClient instâncias. Esta partilha evita o esgotamento do socket.
  • O SocketsHttpHandler ciclos de conexões de acordo com PooledConnectionLifetime para evitar problemas com DNS desatualizado.

Cookies

As instâncias agrupadas HttpMessageHandler resultam em CookieContainer objetos sendo compartilhados. O compartilhamento imprevisto CookieContainer de objetos geralmente resulta em código incorreto. Para aplicações que requerem cookies, considere:

  • Desativar o tratamento automático cookie
  • Evitar IHttpClientFactory

Chamada ConfigurePrimaryHttpMessageHandler para desativar o tratamento automático cookie :

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            UseCookies = false,
        };
    });

Logging

Clientes criados através de IHttpClientFactory registam mensagens de log para todos os pedidos. Habilite o nível de informações apropriado em sua configuração de log para ver as mensagens de log padrão. O registo adicional, como o registo de cabeçalhos de solicitação, só é incluído no nível de traça.

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. As mensagens sufixadas com LogicalHandler ocorrem fora do pipeline do manipulador de solicitações. Na solicitação, as mensagens são registradas antes que qualquer outro manipulador no pipeline as tenha processado. Na resposta, as mensagens são registadas após todos os outros gestores de pipeline terem recebido a resposta.

O registro em log também ocorre dentro do fluxo de processamento do manipulador de solicitações. No exemplo MyNamedClient , essas mensagens são registradas na categoria System.Net.Http.HttpClient.MyNamedClient.ClientHandlerde log . Para a solicitação, isso ocorre depois que todos os outros manipuladores foram executados e imediatamente antes que a solicitação seja enviada na rede. Na resposta, esse log inclui o estado da resposta antes que ela passe de volta pelo pipeline do manipulador.

Habilitar o registo dentro e fora do pipeline permite a inspeção das alterações feitas pelos outros manipuladores de pipeline. Isso pode incluir alterações nos cabeçalhos da solicitação, por exemplo, ou no código de status da resposta.

A inclusão do nome do cliente na categoria de log permite a filtragem de log para clientes nomeados específicos, quando necessário.

Configurar o HttpMessageHandler

Pode ser necessário controlar a configuração interna do HttpMessageHandler utilizado por um cliente.

Um IHttpClientBuilder é retornado ao adicionar clientes nomeados ou digitados. O ConfigurePrimaryHttpMessageHandler método de extensão pode ser usado para definir um delegado. O delegado é usado para criar e configurar o componente principal HttpMessageHandler usado por aquele 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 está registado no contenedor de serviço do Host Genérico.
  • MyService cria uma instância de fábrica de cliente a partir do serviço, que é usada para criar um HttpClient. HttpClient é usado para recuperar uma página da Web.
  • O método do GetPage 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 como chamar serviços de Program.Main, consulte 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 suportado pela comunidade para propagar 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 à porta suportada pela comunidade do pacote HeaderPropagation. ASP.NET Core 3.1 ou posterior suporta 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