Fazer solicitações HTTP usando IHttpClientFactory no ASP.NET CoreMake HTTP requests using IHttpClientFactory in ASP.NET Core

Por Glenn Condron, Ryan Nowak, Steve Gordon, Rick Andersone Kirk LarkinBy Glenn Condron, Ryan Nowak, Steve Gordon, Rick Anderson, and Kirk Larkin

É possível registrar e usar um IHttpClientFactory para configurar e criar instâncias de HttpClient em um aplicativo.An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. o IHttpClientFactory oferece os seguintes benefícios:IHttpClientFactory offers the following benefits:

  • Fornece um local central para nomear e configurar instâncias lógicas de HttpClient.Provides a central location for naming and configuring logical HttpClient instances. Por exemplo, um cliente chamado GitHub pode ser registrado e configurado para acessar o GitHub.For example, a client named github could be registered and configured to access GitHub. Um cliente padrão pode ser registrado para acesso geral.A default client can be registered for general access.
  • Codifica o conceito de middleware de saída por meio da delegação de manipuladores no HttpClient.Codifies the concept of outgoing middleware via delegating handlers in HttpClient. Fornece extensões para o middleware baseado em Polly para tirar proveito da delegação de manipuladores no HttpClient.Provides extensions for Polly-based middleware to take advantage of delegating handlers in HttpClient.
  • Gerencia o pooling e o tempo de vida das instâncias de HttpClientMessageHandler subjacentes.Manages the pooling and lifetime of underlying HttpClientMessageHandler instances. O gerenciamento automático evita problemas comuns de DNS (sistema de nomes de domínio) que ocorrem ao gerenciar manualmente HttpClient tempos de vida.Automatic management avoids common DNS (Domain Name System) problems that occur when manually managing HttpClient lifetimes.
  • Adiciona uma experiência de registro em log configurável (via ILogger) para todas as solicitações enviadas por meio de clientes criados pelo alocador.Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

Exibir ou baixar um código de exemplo (como baixar).View or download sample code (how to download).

O código de exemplo nesta versão de tópico usa System.Text.Json para desserializar o conteúdo JSON retornado em respostas HTTP.The sample code in this topic version uses System.Text.Json to deserialize JSON content returned in HTTP responses. Para obter exemplos que usam Json.NET e ReadAsAsync<T>, use o seletor de versão para selecionar uma versão 2. x deste tópico.For samples that use Json.NET and ReadAsAsync<T>, use the version selector to select a 2.x version of this topic.

Padrões de consumoConsumption patterns

Há várias maneiras de usar o IHttpClientFactory em um aplicativo:There are several ways IHttpClientFactory can be used in an app:

A melhor abordagem depende dos requisitos do aplicativo.The best approach depends upon the app's requirements.

Uso básicoBasic usage

IHttpClientFactory pode ser registrado chamando AddHttpClient:IHttpClientFactory can be registered by calling 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).An IHttpClientFactory can be requested using dependency injection (DI). O código a seguir usa IHttpClientFactory para criar uma instância de HttpClient:The following code uses IHttpClientFactory to create an HttpClient instance:

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/aspnet/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.Using IHttpClientFactory like in the preceding example is a good way to refactor an existing app. Ele não tem impacto sobre como HttpClient é usado.It has no impact on how HttpClient is used. Em locais onde HttpClient instâncias são criadas em um aplicativo existente, substitua essas ocorrências por chamadas para CreateClient.In places where HttpClient instances are created in an existing app, replace those occurrences with calls to CreateClient.

Clientes nomeadosNamed clients

Os clientes nomeados são uma boa opção quando:Named clients are a good choice when:

  • O aplicativo requer muitos usos distintos de HttpClient.The app requires many distinct uses of HttpClient.
  • Muitos HttpClients têm configuração diferente.Many HttpClients have different configuration.

A configuração para um HttpClient nomeado pode ser especificada durante o registro no Startup.ConfigureServices:Configuration for a named HttpClient can be specified during registration in 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 está configurado com:In the preceding code the client is configured with:

  • O endereço base https://api.github.com/.The base address https://api.github.com/.
  • Dois cabeçalhos necessários para trabalhar com a API do GitHub.Two headers required to work with the GitHub API.

CreateClientCreateClient

Cada vez que CreateClient é chamado:Each time CreateClient is called:

  • Uma nova instância do HttpClient é criada.A new instance of HttpClient is created.
  • A ação de configuração é chamada.The configuration action is called.

Para criar um cliente nomeado, passe seu nome para CreateClient:To create a named client, pass its name into 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/aspnet/AspNetCore.Docs/pulls");

        var client = _clientFactory.CreateClient("github");

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            PullRequests = await JsonSerializer.DeserializeAsync
                    <IEnumerable<GitHubPullRequest>>(responseStream);
        }
        else
        {
            GetPullRequestsError = true;
            PullRequests = Array.Empty<GitHubPullRequest>();
        }
    }
}

No código anterior, a solicitação não precisa especificar um nome do host.In the preceding code, the request doesn't need to specify a hostname. O código pode passar apenas o caminho, já que o endereço base configurado para o cliente é usado.The code can pass just the path, since the base address configured for the client is used.

Clientes com tipoTyped clients

Clientes com tipo:Typed clients:

  • Fornecem as mesmas funcionalidade que os clientes nomeados sem a necessidade de usar cadeias de caracteres como chaves.Provide the same capabilities as named clients without the need to use strings as keys.
  • Fornecem a ajuda do IntelliSense e do compilador durante o consumo de clientes.Provides IntelliSense and compiler help when consuming clients.
  • Fornecem um único local para configurar e interagir com um determinado HttpClient.Provide a single location to configure and interact with a particular HttpClient. Por exemplo, um único cliente tipado pode ser usado:For example, a single typed client might be used:
    • Para um ponto de extremidade de back-end único.For a single backend endpoint.
    • Para encapsular toda a lógica lidando com o ponto de extremidade.To encapsulate all logic dealing with the endpoint.
  • Trabalhe com DI e pode ser injetado onde necessário no aplicativo.Work with DI and can be injected where required in the app.

Um cliente tipado aceita um parâmetro HttpClient em seu construtor:A typed client accepts an HttpClient parameter in its constructor:

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/aspnet/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);
    }
}

No código anterior:In the preceding code:

  • A configuração é movida para o cliente digitado.The configuration is moved into the typed client.
  • O objeto HttpClient é exposto como uma propriedade pública.The HttpClient object is exposed as a public property.

Métodos específicos de API podem ser criados para expor HttpClient funcionalidade.API-specific methods can be created that expose HttpClient functionality. Por exemplo, o método GetAspNetDocsIssues encapsula o código para recuperar problemas abertos.For example, the GetAspNetDocsIssues method encapsulates code to retrieve open issues.

O código a seguir chama AddHttpClient em Startup.ConfigureServices para registrar uma classe de cliente tipada:The following code calls AddHttpClient in Startup.ConfigureServices to register a typed client class:

services.AddHttpClient<GitHubService>();

O cliente com tipo é registrado como transitório com a DI.The typed client is registered as transient with DI. O cliente com tipo pode ser injetado e consumido diretamente:The typed client can be injected and consumed directly:

public class TypedClientModel : PageModel
{
    private readonly GitHubService _gitHubService;

    public IEnumerable<GitHubIssue> LatestIssues { get; private set; }

    public bool HasIssue => LatestIssues.Any();

    public bool GetIssuesError { get; private set; }

    public TypedClientModel(GitHubService gitHubService)
    {
        _gitHubService = gitHubService;
    }

    public async Task OnGet()
    {
        try
        {
            LatestIssues = await _gitHubService.GetAspNetDocsIssues();
        }
        catch(HttpRequestException)
        {
            GetIssuesError = true;
            LatestIssues = Array.Empty<GitHubIssue>();
        }
    }
}

A configuração de um cliente tipado pode ser especificada durante o registro no Startup.ConfigureServices, em vez de no construtor do cliente digitado:The configuration for a typed client can be specified during registration in Startup.ConfigureServices, rather than in the typed client's constructor:

services.AddHttpClient<RepoService>(c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

O HttpClient pode ser encapsulado em um cliente digitado.The HttpClient can be encapsulated within a typed client. Em vez de expô-lo como uma propriedade, defina um método que chame a instância de HttpClient internamente:Rather than exposing it as a property, define a method which calls the HttpClient instance internally:

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, a HttpClient é armazenada em um campo particular.In the preceding code, the HttpClient is stored in a private field. O acesso ao HttpClient é pelo método GetRepos público.Access to the HttpClient is by the public GetRepos method.

Clientes geradosGenerated clients

IHttpClientFactory pode ser usado em combinação com bibliotecas de terceiros, como rEFIt.IHttpClientFactory can be used in combination with third-party libraries such as Refit. Refit é uma biblioteca REST para .NET.Refit is a REST library for .NET. Ela converte APIs REST em interfaces dinâmicas.It converts REST APIs into live interfaces. Uma implementação da interface é gerada dinamicamente pelo RestService usando HttpClient para fazer as chamadas de HTTP externas.An implementation of the interface is generated dynamically by the RestService, using HttpClient to make the external HTTP calls.

Uma interface e uma resposta são definidas para representar a API externa e sua resposta:An interface and a reply are defined to represent the external API and its response:

public interface IHelloClient
{
    [Get("/helloworld")]
    Task<Reply> GetMessageAsync();
}

public class Reply
{
    public string Message { get; set; }
}

Um cliente com tipo pode ser adicionado usando a Refit para gerar a implementação:A typed client can be added, using Refit to generate the implementation:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("http://localhost:5000");
    })
    .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));

    services.AddControllers();
}

A interface definida pode ser consumida, quando necessário, com a implementação fornecida pela DI e pela Refit:The defined interface can be consumed where necessary, with the implementation provided by DI and 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ídaOutgoing request middleware

HttpClient tem o conceito de delegar manipuladores que podem ser vinculados para solicitações HTTP de saída.HttpClient has the concept of delegating handlers that can be linked together for outgoing HTTP requests. IHttpClientFactory:IHttpClientFactory:

  • Simplifica a definição dos manipuladores a serem aplicados para cada cliente nomeado.Simplifies defining the handlers to apply for each named client.

  • Dá suporte ao registro e encadeamento de vários manipuladores para criar um pipeline de middleware de solicitação de saída.Supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. Cada um desses manipuladores é capaz de executar o trabalho antes e após a solicitação de saída.Each of these handlers is able to perform work before and after the outgoing request. Esse padrão:This pattern:

    • É semelhante ao pipeline de middleware de entrada no ASP.NET Core.Is similar to the inbound middleware pipeline in ASP.NET Core.

    • Fornece um mecanismo para gerenciar preocupações abrangentes em relação a solicitações HTTP, como:Provides a mechanism to manage cross-cutting concerns around HTTP requests, such as:

      • cachingcaching
      • tratamento de erroserror handling
      • serializaçãoserialization
      • loglogging

Para criar um manipulador de delegação:To create a delegating handler:

  • Derive de DelegatingHandler.Derive from DelegatingHandler.
  • Substitua SendAsync.Override SendAsync. Execute o código antes de passar a solicitação para o próximo manipulador no pipeline:Execute code before passing the request to the next handler in the pipeline:
public class ValidateHeaderHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        if (!request.Headers.Contains("X-API-KEY"))
        {
            return new HttpResponseMessage(HttpStatusCode.BadRequest)
            {
                Content = new StringContent(
                    "You must supply an API key header called X-API-KEY")
            };
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

O código anterior verifica se o cabeçalho de X-API-KEY está na solicitação.The preceding code checks if the X-API-KEY header is in the request. Se X-API-KEY estiver ausente, BadRequest será retornado.If X-API-KEY is missing, BadRequest is returned.

Mais de um manipulador pode ser adicionado à configuração para um HttpClient com Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:More than one handler can be added to the configuration for an HttpClient with Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<ValidateHeaderHandler>();

    services.AddHttpClient("externalservice", c =>
    {
        // Assume this is an "external" service which requires an API KEY
        c.BaseAddress = new Uri("https://localhost:5001/");
    })
    .AddHttpMessageHandler<ValidateHeaderHandler>();

    // Remaining code deleted for brevity.

No código anterior, o ValidateHeaderHandler é registrado com a DI.In the preceding code, the ValidateHeaderHandler is registered with DI. O IHttpClientFactory cria um escopo de injeção de dependência separado para cada manipulador.The IHttpClientFactory creates a separate DI scope for each handler. Os manipuladores podem depender dos serviços de qualquer escopo.Handlers can depend upon services of any scope. Os serviços dos quais os manipuladores dependem são descartados quando o manipulador é descartado.Services that handlers depend upon are disposed when the handler is disposed.

Depois de registrado, o AddHttpMessageHandler poderá ser chamado, passando o tipo para o manipulador.Once registered, AddHttpMessageHandler can be called, passing in the type for the handler.

Vários manipuladores podem ser registrados na ordem em que eles devem ser executados.Multiple handlers can be registered in the order that they should execute. Cada manipulador encapsula o próximo manipulador até que o HttpClientHandler final execute a solicitação:Each handler wraps the next handler until the final HttpClientHandler executes the request:

services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();

services.AddHttpClient("clientwithhandlers")
    // This handler is on the outside and called first during the 
    // request, last during the response.
    .AddHttpMessageHandler<SecureRequestHandler>()
    // This handler is on the inside, closest to the request being 
    // sent.
    .AddHttpMessageHandler<RequestDataHandler>();

Use uma das seguintes abordagens para compartilhar o estado por solicitação com os manipuladores de mensagens:Use one of the following approaches to share per-request state with message handlers:

Usar manipuladores baseados no PollyUse Polly-based handlers

o IHttpClientFactory integra-se com a biblioteca de terceiros Polly.IHttpClientFactory integrates with the third-party library Polly. O Polly é uma biblioteca abrangente de tratamento de falha transitória e de resiliência para .NET.Polly is a comprehensive resilience and transient fault-handling library for .NET. Ela permite que os desenvolvedores expressem políticas, como Repetição, Disjuntor, Tempo Limite, Isolamento de Bulkhead e Fallback de maneira fluente e thread-safe.It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Os métodos de extensão são fornecidos para habilitar o uso de políticas do Polly com instâncias de HttpClient configuradas.Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. As extensões Polly dão suporte à adição de manipuladores baseados em Polly a clientes.The Polly extensions support adding Polly-based handlers to clients. Polly requer o pacote NuGet Microsoft. Extensions. http. Polly .Polly requires the Microsoft.Extensions.Http.Polly NuGet package.

Tratar falhas transitóriasHandle transient faults

As falhas normalmente ocorrem quando chamadas HTTP externas são transitórias.Faults typically occur when external HTTP calls are transient. AddTransientHttpErrorPolicy permite que uma política seja definida para lidar com erros transitórios.AddTransientHttpErrorPolicy allows a policy to be defined to handle transient errors. As políticas configuradas com AddTransientHttpErrorPolicy manipulam as seguintes respostas:Policies configured with AddTransientHttpErrorPolicy handle the following responses:

AddTransientHttpErrorPolicy fornece acesso a um objeto PolicyBuilder configurado para tratar erros que representam uma possível falha transitória:AddTransientHttpErrorPolicy provides access to a PolicyBuilder object configured to handle errors representing a possible transient fault:

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient<UnreliableEndpointCallerService>()
        .AddTransientHttpErrorPolicy(p => 
            p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

    // Remaining code deleted for brevity.

No código anterior, uma política WaitAndRetryAsync é definida.In the preceding code, a WaitAndRetryAsync policy is defined. As solicitações com falha são repetidas até três vezes com um atraso de 600 ms entre as tentativas.Failed requests are retried up to three times with a delay of 600 ms between attempts.

Selecionar políticas dinamicamenteDynamically select policies

Os métodos de extensão são fornecidos para adicionar manipuladores baseados em Polly, por exemplo, AddPolicyHandler.Extension methods are provided to add Polly-based handlers, for example, AddPolicyHandler. A sobrecarga de AddPolicyHandler a seguir inspeciona a solicitação para decidir qual política aplicar:The following AddPolicyHandler overload inspects the request to decide which policy to apply:

var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
    .AddPolicyHandler(request => 
        request.Method == HttpMethod.Get ? timeout : longTimeout);

No código anterior, se a solicitação de saída é um HTTP GET, um tempo limite de 10 segundos é aplicado.In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. Para qualquer outro método HTTP, é usado um tempo limite de 30 segundos.For any other HTTP method, a 30-second timeout is used.

Adicionar vários manipuladores do PollyAdd multiple Polly handlers

É comum aninhar políticas Polly:It's common to nest Polly policies:

services.AddHttpClient("multiplepolicies")
    .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

No exemplo anterior:In the preceding example:

  • Dois manipuladores são adicionados.Two handlers are added.
  • O primeiro manipulador usa AddTransientHttpErrorPolicy para adicionar uma política de repetição.The first handler uses AddTransientHttpErrorPolicy to add a retry policy. As solicitações com falha são repetidas até três vezes.Failed requests are retried up to three times.
  • A segunda chamada de AddTransientHttpErrorPolicy adiciona uma política de disjuntor.The second AddTransientHttpErrorPolicy call adds a circuit breaker policy. Outras solicitações externas serão bloqueadas por 30 segundos se 5 tentativas com falha ocorrerem em sequência.Further external requests are blocked for 30 seconds if 5 failed attempts occur sequentially. As políticas de disjuntor são políticas com estado.Circuit breaker policies are stateful. Todas as chamadas por meio desse cliente compartilham o mesmo estado do circuito.All calls through this client share the same circuit state.

Adicionar políticas do registro do PollyAdd policies from the Polly registry

Uma abordagem para gerenciar as políticas usadas com frequência é defini-las uma única vez e registrá-las em um PolicyRegistry.An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry.

No seguinte código:In the following code:

public void ConfigureServices(IServiceCollection services)
{           
    var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
        TimeSpan.FromSeconds(10));
    var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
        TimeSpan.FromSeconds(30));
    
    var registry = services.AddPolicyRegistry();

    registry.Add("regular", timeout);
    registry.Add("long", longTimeout);
    
    services.AddHttpClient("regularTimeoutHandler")
        .AddPolicyHandlerFromRegistry("regular");

    services.AddHttpClient("longTimeoutHandler")
       .AddPolicyHandlerFromRegistry("long");

    // Remaining code deleted for brevity.

Para obter mais informações sobre as integrações IHttpClientFactory e Polly, consulte o wiki do Polly.For more information on IHttpClientFactory and Polly integrations, see the Polly wiki.

HttpClient e gerenciamento de tempo de vidaHttpClient and lifetime management

Uma nova instância de HttpClient é chamada, sempre que CreateClient é chamado na IHttpClientFactory.A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. Um HttpMessageHandler é criado por cliente nomeado.An HttpMessageHandler is created per named client. O alocador gerencia a vida útil das instâncias do HttpMessageHandler.The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory cria pools com as instâncias de HttpMessageHandler criadas pelo alocador para reduzir o consumo de recursos.IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. Uma instância de HttpMessageHandler poderá ser reutilizada no pool, ao criar uma nova instância de HttpClient, se o respectivo tempo de vida não tiver expirado.An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn't expired.

O pooling de manipuladores é preferível porque normalmente cada manipulador gerencia as próprias conexões HTTP subjacentes.Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. Criar mais manipuladores do que o necessário pode resultar em atrasos de conexão.Creating more handlers than necessary can result in connection delays. Alguns manipuladores também mantêm as conexões abertas indefinidamente, o que pode impedir que o manipulador reajam às alterações de DNS (sistema de nomes de domínio).Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS (Domain Name System) changes.

O tempo de vida padrão do manipulador é de 2 minutos.The default handler lifetime is two minutes. O valor padrão pode ser substituído em uma base de cliente por nome:The default value can be overridden on a per named client basis:

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

    // Remaining code deleted for brevity.

instâncias de HttpClient geralmente podem ser tratadas como objetos .NET que não exigem descarte.HttpClient instances can generally be treated as .NET objects not requiring disposal. O descarte cancela as solicitações de saída e garante que a determinada instância de HttpClient não seja usada depois de chamar Dispose.Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory rastreia e descarta recursos usados pelas instâncias de HttpClient.IHttpClientFactory tracks and disposes resources used by HttpClient instances.

Manter uma única instância de HttpClient ativa por uma longa duração é um padrão comum usado, antes do início de IHttpClientFactory.Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. Esse padrão se torna desnecessário após a migração para IHttpClientFactory.This pattern becomes unnecessary after migrating to IHttpClientFactory.

Alternativas para IHttpClientFactoryAlternatives to IHttpClientFactory

O uso de IHttpClientFactory em um aplicativo habilitado para DI é evitado:Using IHttpClientFactory in a DI-enabled app avoids:

  • Problemas de esgotamento de recursos por meio do pooling HttpMessageHandler instâncias.Resource exhaustion problems by pooling HttpMessageHandler instances.
  • Problemas de DNS obsoletos por ciclo HttpMessageHandler instâncias em intervalos regulares.Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

Há maneiras alternativas de resolver os problemas anteriores usando uma instância de SocketsHttpHandler de vida longa.There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • Crie uma instância do SocketsHttpHandler quando o aplicativo for iniciado e usado para a vida útil do aplicativo.Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configure PooledConnectionLifetime para um valor apropriado com base em tempos de atualização de DNS.Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Crie HttpClient instâncias usando new HttpClient(handler, disposeHandler: false) conforme necessário.Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

As abordagens anteriores resolvem os problemas de gerenciamento de recursos que IHttpClientFactory resolve de forma semelhante.The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • O SocketsHttpHandler compartilha conexões entre instâncias de HttpClient.The SocketsHttpHandler shares connections across HttpClient instances. Esse compartilhamento impede o esgotamento de soquete.This sharing prevents socket exhaustion.
  • O SocketsHttpHandler ciclos conexões de acordo com PooledConnectionLifetime para evitar problemas de DNS obsoletos.The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

CookiesCookies

As instâncias de HttpMessageHandler em pool resultam em CookieContainer objetos que estão sendo compartilhados.The pooled HttpMessageHandler instances results in CookieContainer objects being shared. O compartilhamento de objetos CookieContainer imprevistos geralmente resulta em código incorreto.Unanticipated CookieContainer object sharing often results in incorrect code. Para aplicativos que exigem cookies, considere o:For apps that require cookies, consider either:

  • Desabilitando a manipulação automática de cookiesDisabling automatic cookie handling
  • Evitando IHttpClientFactoryAvoiding IHttpClientFactory

Chame ConfigurePrimaryHttpMessageHandler para desabilitar o tratamento automático de cookies:Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

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

Registrando em logLogging

Os clientes criado pelo IHttpClientFactory registram mensagens de log para todas as solicitações.Clients created via IHttpClientFactory record log messages for all requests. Habilite o nível de informações apropriado na configuração de log para ver as mensagens de log padrão.Enable the appropriate information level in the logging configuration to see the default log messages. Os registros em log adicionais, como o registro em log dos cabeçalhos de solicitação, estão incluídos somente no nível de rastreamento.Additional logging, such as the logging of request headers, is only included at trace level.

A categoria de log usada para cada cliente inclui o nome do cliente.The log category used for each client includes the name of the client. Um cliente chamado MyNamedClient, por exemplo, registra mensagens com uma categoria de "System .net. http. HttpClient. MyNamedClient. LogicalHandler".A client named MyNamedClient, for example, logs messages with a category of "System.Net.Http.HttpClient.MyNamedClient.LogicalHandler". Mensagens sufixadas com LogicalHandler ocorrem fora do pipeline do manipulador de solicitações.Messages suffixed with LogicalHandler occur outside the request handler pipeline. Na solicitação, as mensagens são registradas em log antes que qualquer outro manipulador no pipeline a tenha processado.On the request, messages are logged before any other handlers in the pipeline have processed it. Na resposta, as mensagens são registradas depois que qualquer outro manipulador do pipeline tenha recebido a resposta.On the response, messages are logged after any other pipeline handlers have received the response.

O registro em log também ocorre dentro do pipeline do manipulador de solicitações.Logging also occurs inside the request handler pipeline. No exemplo de MyNamedClient , essas mensagens são registradas com a categoria de log "System .net. http. HttpClient. MyNamedClient. ClientHandler".In the MyNamedClient example, those messages are logged with the log category "System.Net.Http.HttpClient.MyNamedClient.ClientHandler". Para a solicitação, isso ocorre depois que todos os outros manipuladores forem executados e imediatamente antes de a solicitação ser enviada.For the request, this occurs after all other handlers have run and immediately before the request is sent. Na resposta, esse registro em log inclui o estado da resposta antes que ela seja retornada por meio do pipeline do manipulador.On the response, this logging includes the state of the response before it passes back through the handler pipeline.

Habilitar o registro em log dentro e fora do pipeline permite a inspeção das alterações feitas por outros manipuladores do pipeline.Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. Isso pode incluir alterações nos cabeçalhos de solicitação ou no código de status de resposta.This may include changes to request headers or to the response status code.

A inclusão do nome do cliente na categoria de log permite a filtragem de log para clientes nomeados específicos.Including the name of the client in the log category enables log filtering for specific named clients.

Configurar o HttpMessageHandlerConfigure the HttpMessageHandler

Talvez seja necessário controlar a configuração do HttpMessageHandler interno usado por um cliente.It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

Um IHttpClientBuilder é retornado ao adicionar clientes nomeados ou com tipo.An IHttpClientBuilder is returned when adding named or typed clients. O método de extensão ConfigurePrimaryHttpMessageHandler pode ser usado para definir um representante.The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. O representante que é usado para criar e configurar o HttpMessageHandler primário usado pelo cliente:The delegate is used to create and configure the primary HttpMessageHandler used by that client:

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 consoleUse IHttpClientFactory in a console app

Em um aplicativo de console, adicione as seguintes referências de pacote ao projeto:In a console app, add the following package references to the project:

No exemplo a seguir:In the following example:

  • IHttpClientFactory é registrado no contêiner de serviço do Host genérico.IHttpClientFactory is registered in the Generic Host's service container.
  • MyService cria uma instância de fábrica do cliente do serviço, que é usada para criar um HttpClient.MyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient é usado para recuperar uma página da Web.HttpClient is used to retrieve a webpage.
  • Main cria um escopo para executar o método GetPage do serviço e gravar os primeiros 500 caracteres do conteúdo da página da Web no console.Main creates a scope to execute the service's GetPage method and write the first 500 characters of the webpage content to the 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();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myService = services.GetRequiredService<IMyService>();
                var pageContent = await myService.GetPage();

                Console.WriteLine(pageContent.Substring(0, 500));
            }
            catch (Exception ex)
            {
                var logger = 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}";
            }
        }
    }
}

Recursos adicionaisAdditional resources

Por Glenn Condron, Ryan Nowak e Steve GordonBy Glenn Condron, Ryan Nowak, and Steve Gordon

É possível registrar e usar um IHttpClientFactory para configurar e criar instâncias de HttpClient em um aplicativo.An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. Ele oferece os seguintes benefícios:It offers the following benefits:

  • Fornece um local central para nomear e configurar instâncias lógicas de HttpClient.Provides a central location for naming and configuring logical HttpClient instances. Por exemplo, um cliente do github pode ser registrado e configurado para acessar o GitHub.For example, a github client can be registered and configured to access GitHub. Um cliente padrão pode ser registrado para outras finalidades.A default client can be registered for other purposes.
  • Codifica o conceito de middleware de saída por meio da delegação de manipuladores no HttpClient e fornece extensões para que o middleware baseado no Polly possa aproveitar esse recurso.Codifies the concept of outgoing middleware via delegating handlers in HttpClient and provides extensions for Polly-based middleware to take advantage of that.
  • Gerencia o pooling e o tempo de vida das instâncias de HttpClientMessageHandler subjacentes para evitar problemas de DNS comuns que ocorrem no gerenciamento manual de tempos de vida de HttpClient.Manages the pooling and lifetime of underlying HttpClientMessageHandler instances to avoid common DNS problems that occur when manually managing HttpClient lifetimes.
  • Adiciona uma experiência de registro em log configurável (via ILogger) para todas as solicitações enviadas por meio de clientes criados pelo alocador.Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

Exibir ou baixar código de exemplo (como baixar)View or download sample code (how to download)

Padrões de consumoConsumption patterns

Há várias maneiras de usar o IHttpClientFactory em um aplicativo:There are several ways IHttpClientFactory can be used in an app:

Nenhum deles é estritamente superiores ao outro.None of them are strictly superior to another. A melhor abordagem depende das restrições do aplicativo.The best approach depends upon the app's constraints.

Uso básicoBasic usage

O IHttpClientFactory pode ser registrado chamando o método de extensão AddHttpClient em IServiceCollection, dentro do método Startup.ConfigureServices.The IHttpClientFactory can be registered by calling the AddHttpClient extension method on the IServiceCollection, inside the Startup.ConfigureServices method.

services.AddHttpClient();

Depois de registrado, o código pode aceitar um IHttpClientFactory em qualquer lugar em que os serviços possam ser injetados com injeção de dependência.Once registered, code can accept an IHttpClientFactory anywhere services can be injected with dependency injection (DI). O IHttpClientFactory pode ser usado para criar uma instância de HttpClient:The IHttpClientFactory can be used to create an HttpClient instance:

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/aspnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            Branches = await response.Content
                .ReadAsAsync<IEnumerable<GitHubBranch>>();
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }                               
    }
}

Usar o IHttpClientFactory dessa forma é uma ótima maneira de refatorar um aplicativo existente.Using IHttpClientFactory in this fashion is a good way to refactor an existing app. Não há nenhum impacto na maneira em que o HttpClient é usado.It has no impact on the way HttpClient is used. Nos locais em que as instâncias de HttpClient são criadas no momento, substitua essas ocorrências por uma chamada a CreateClient.In places where HttpClient instances are currently created, replace those occurrences with a call to CreateClient.

Clientes nomeadosNamed clients

Quando um aplicativo exige vários usos distintos do HttpClient, cada um com uma configuração diferente, uma opção é usar clientes nomeados.If an app requires many distinct uses of HttpClient, each with a different configuration, an option is to use named clients. A configuração de um HttpClient nomeado pode ser especificada durante o registro em Startup.ConfigureServices.Configuration for a named HttpClient can be specified during registration in 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.In the preceding code, AddHttpClient is called, providing the name github. Esse cliente tem algumas configurações padrão aplicadas, ou seja, o endereço básico e dois cabeçalhos necessários para trabalhar com a API do GitHub.This client has some default configuration applied—namely the base address and two headers required to work with the GitHub API.

Toda vez que o CreateClient é chamado, uma nova instância de HttpClient é criada e a ação de configuração é chamada.Each time CreateClient is called, a new instance of HttpClient is created and the configuration action is called.

Para consumir um cliente nomeado, um parâmetro de cadeia de caracteres pode ser passado para CreateClient.To consume a named client, a string parameter can be passed to CreateClient. Especifique o nome do cliente a ser criado:Specify the name of the client to be created:

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/aspnet/AspNetCore.Docs/pulls");

        var client = _clientFactory.CreateClient("github");

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            PullRequests = await response.Content
                .ReadAsAsync<IEnumerable<GitHubPullRequest>>();
        }
        else
        {
            GetPullRequestsError = true;
            PullRequests = Array.Empty<GitHubPullRequest>();
        }
    }
}

No código anterior, a solicitação não precisa especificar um nome do host.In the preceding code, the request doesn't need to specify a hostname. Ela pode passar apenas o caminho, pois o endereço básico configurado para o cliente é usado.It can pass just the path, since the base address configured for the client is used.

Clientes com tipoTyped clients

Clientes com tipo:Typed clients:

  • Fornecem as mesmas funcionalidade que os clientes nomeados sem a necessidade de usar cadeias de caracteres como chaves.Provide the same capabilities as named clients without the need to use strings as keys.
  • Fornecem a ajuda do IntelliSense e do compilador durante o consumo de clientes.Provides IntelliSense and compiler help when consuming clients.
  • Fornecem um único local para configurar e interagir com um determinado HttpClient.Provide a single location to configure and interact with a particular HttpClient. Por exemplo, um único cliente com tipo pode ser usado para um único ponto de extremidade de back-end e encapsular toda a lógica que lida com esse ponto de extremidade.For example, a single typed client might be used for a single backend endpoint and encapsulate all logic dealing with that endpoint.
  • Funcionam com a DI e podem ser injetados no local necessário no aplicativo.Work with DI and can be injected where required in your app.

Um cliente tipado aceita um parâmetro HttpClient em seu construtor:A typed client accepts an HttpClient parameter in its constructor:

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/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");

        response.EnsureSuccessStatusCode();

        var result = await response.Content
            .ReadAsAsync<IEnumerable<GitHubIssue>>();

        return result;
    }
}

No código anterior, a configuração é movida para o cliente com tipo.In the preceding code, the configuration is moved into the typed client. O objeto HttpClient é exposto como uma propriedade pública.The HttpClient object is exposed as a public property. É possível definir métodos específicos da API que expõem a funcionalidade HttpClient.It's possible to define API-specific methods that expose HttpClient functionality. O método GetAspNetDocsIssues encapsula o código necessário para consultar e analisar os últimos problemas em aberto de um repositório GitHub.The GetAspNetDocsIssues method encapsulates the code needed to query for and parse out the latest open issues from a GitHub repository.

Para registrar um cliente com tipo, o método de extensão AddHttpClient genérico pode ser usado em Startup.ConfigureServices, especificando a classe do cliente com tipo:To register a typed client, the generic AddHttpClient extension method can be used within Startup.ConfigureServices, specifying the typed client class:

services.AddHttpClient<GitHubService>();

O cliente com tipo é registrado como transitório com a DI.The typed client is registered as transient with DI. O cliente com tipo pode ser injetado e consumido diretamente:The typed client can be injected and consumed directly:

public class TypedClientModel : PageModel
{
    private readonly GitHubService _gitHubService;

    public IEnumerable<GitHubIssue> LatestIssues { get; private set; }

    public bool HasIssue => LatestIssues.Any();

    public bool GetIssuesError { get; private set; }

    public TypedClientModel(GitHubService gitHubService)
    {
        _gitHubService = gitHubService;
    }

    public async Task OnGet()
    {
        try
        {
            LatestIssues = await _gitHubService.GetAspNetDocsIssues();
        }
        catch(HttpRequestException)
        {
            GetIssuesError = true;
            LatestIssues = Array.Empty<GitHubIssue>();
        }
    }
}

Se preferir, a configuração de um cliente com tipo poderá ser especificada durante o registro em Startup.ConfigureServices e não no construtor do cliente com tipo:If preferred, the configuration for a typed client can be specified during registration in Startup.ConfigureServices, rather than in the typed client's constructor:

services.AddHttpClient<RepoService>(c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

É possível encapsular totalmente o HttpClient dentro de um cliente com tipo.It's possible to entirely encapsulate the HttpClient within a typed client. Em vez de o expor como uma propriedade, é possível fornecer métodos públicos que chamam a instância de HttpClient internamente.Rather than exposing it as a property, public methods can be provided which call the HttpClient instance internally.

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.In the preceding code, the HttpClient is stored as a private field. Todo o acesso para fazer chamadas externas passa pelo método GetRepos.All access to make external calls goes through the GetRepos method.

Clientes geradosGenerated clients

O IHttpClientFactory pode ser usado em combinação com outras bibliotecas de terceiros, como a Refit.IHttpClientFactory can be used in combination with other third-party libraries such as Refit. Refit é uma biblioteca REST para .NET.Refit is a REST library for .NET. Ela converte APIs REST em interfaces dinâmicas.It converts REST APIs into live interfaces. Uma implementação da interface é gerada dinamicamente pelo RestService usando HttpClient para fazer as chamadas de HTTP externas.An implementation of the interface is generated dynamically by the RestService, using HttpClient to make the external HTTP calls.

Uma interface e uma resposta são definidas para representar a API externa e sua resposta:An interface and a reply are defined to represent the external API and its response:

public interface IHelloClient
{
    [Get("/helloworld")]
    Task<Reply> GetMessageAsync();
}

public class Reply
{
    public string Message { get; set; }
}

Um cliente com tipo pode ser adicionado usando a Refit para gerar a implementação:A typed client can be added, using Refit to generate the implementation:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("https://localhost:5001");
    })
    .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));

    services.AddMvc();
}

A interface definida pode ser consumida, quando necessário, com a implementação fornecida pela DI e pela Refit:The defined interface can be consumed where necessary, with the implementation provided by DI and 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ídaOutgoing request middleware

O HttpClient já tem o conceito de delegar manipuladores que podem ser vinculados para solicitações HTTP de saída.HttpClient already has the concept of delegating handlers that can be linked together for outgoing HTTP requests. O IHttpClientFactory facilita a definição dos manipuladores a serem aplicados a cada cliente nomeado.The IHttpClientFactory makes it easy to define the handlers to apply for each named client. Ele permite o registro e o encadeamento de vários manipuladores para criar um pipeline do middleware de solicitação saída.It supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. Cada um desses manipuladores é capaz de executar o trabalho antes e após a solicitação de saída.Each of these handlers is able to perform work before and after the outgoing request. Esse padrão é semelhante ao pipeline do middleware de entrada no ASP.NET Core.This pattern is similar to the inbound middleware pipeline in ASP.NET Core. O padrão fornece um mecanismo para gerenciar interesses paralelos em relação às solicitações HTTP, incluindo o armazenamento em cache, o tratamento de erro, a serialização e o registro em log.The pattern provides a mechanism to manage cross-cutting concerns around HTTP requests, including caching, error handling, serialization, and logging.

Para criar um manipulador, defina uma classe derivando-a de DelegatingHandler.To create a handler, define a class deriving from DelegatingHandler. Substitua o método SendAsync para executar o código antes de passar a solicitação para o próximo manipulador no pipeline:Override the SendAsync method to execute code before passing the request to the next handler in the 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.The preceding code defines a basic handler. Ele verifica se um cabeçalho X-API-KEY foi incluído na solicitação.It checks to see if an X-API-KEY header has been included on the request. Se o cabeçalho estiver ausente, isso poderá evitar a chamada de HTTP e retornar uma resposta adequada.If the header is missing, it can avoid the HTTP call and return a suitable response.

Durante o registro, um ou mais manipuladores podem ser adicionados à configuração para um HttpClient.During registration, one or more handlers can be added to the configuration for an HttpClient. Essa tarefa é realizada por meio de métodos de extensão no IHttpClientBuilder.This task is accomplished via extension methods on the IHttpClientBuilder.

services.AddTransient<ValidateHeaderHandler>();

services.AddHttpClient("externalservice", c =>
{
    // Assume this is an "external" service which requires an API KEY
    c.BaseAddress = new Uri("https://localhost:5000/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();

No código anterior, o ValidateHeaderHandler é registrado com a DI.In the preceding code, the ValidateHeaderHandler is registered with DI. O IHttpClientFactory cria um escopo de injeção de dependência separado para cada manipulador.The IHttpClientFactory creates a separate DI scope for each handler. Os manipuladores podem depender de serviços de qualquer escopo.Handlers are free to depend upon services of any scope. Os serviços dos quais os manipuladores dependem são descartados quando o manipulador é descartado.Services that handlers depend upon are disposed when the handler is disposed.

Depois de registrado, o AddHttpMessageHandler poderá ser chamado, passando o tipo para o manipulador.Once registered, AddHttpMessageHandler can be called, passing in the type for the handler.

Vários manipuladores podem ser registrados na ordem em que eles devem ser executados.Multiple handlers can be registered in the order that they should execute. Cada manipulador encapsula o próximo manipulador até que o HttpClientHandler final execute a solicitação:Each handler wraps the next handler until the final HttpClientHandler executes the request:

services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();

services.AddHttpClient("clientwithhandlers")
    // This handler is on the outside and called first during the 
    // request, last during the response.
    .AddHttpMessageHandler<SecureRequestHandler>()
    // This handler is on the inside, closest to the request being 
    // sent.
    .AddHttpMessageHandler<RequestDataHandler>();

Use uma das seguintes abordagens para compartilhar o estado por solicitação com os manipuladores de mensagens:Use one of the following approaches to share per-request state with message handlers:

  • Passe os dados pelo manipulador usando HttpRequestMessage.Properties.Pass data into the handler using HttpRequestMessage.Properties.
  • Use IHttpContextAccessor para acessar a solicitação atual.Use IHttpContextAccessor to access the current request.
  • Crie um objeto de armazenamento AsyncLocal personalizado para passar os dados.Create a custom AsyncLocal storage object to pass the data.

Usar manipuladores baseados no PollyUse Polly-based handlers

O IHttpClientFactory integra-se a uma biblioteca de terceiros popular chamada Polly.IHttpClientFactory integrates with a popular third-party library called Polly. O Polly é uma biblioteca abrangente de tratamento de falha transitória e de resiliência para .NET.Polly is a comprehensive resilience and transient fault-handling library for .NET. Ela permite que os desenvolvedores expressem políticas, como Repetição, Disjuntor, Tempo Limite, Isolamento de Bulkhead e Fallback de maneira fluente e thread-safe.It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Os métodos de extensão são fornecidos para habilitar o uso de políticas do Polly com instâncias de HttpClient configuradas.Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. As extensões do Polly:The Polly extensions:

  • Dão suporte à adição de manipuladores baseados no Polly aos clientes.Support adding Polly-based handlers to clients.
  • Podem ser usadas depois de instalar o pacote do NuGet Microsoft.Extensions.Http.Polly.Can be used after installing the Microsoft.Extensions.Http.Polly NuGet package. O pacote não está incluído na estrutura ASP.NET Core compartilhada.The package isn't included in the ASP.NET Core shared framework.

Tratar falhas transitóriasHandle transient faults

As falhas mais comuns ocorrem quando as chamadas HTTP externas são transitórias.Most common faults occur when external HTTP calls are transient. Um método de extensão conveniente chamado AddTransientHttpErrorPolicy é incluído para permitir que uma política seja definido para tratar erros transitórios.A convenient extension method called AddTransientHttpErrorPolicy is included which allows a policy to be defined to handle transient errors. As políticas configuradas com esse método de extensão tratam HttpRequestException, respostas HTTP 5xx e respostas HTTP 408.Policies configured with this extension method handle HttpRequestException, HTTP 5xx responses, and HTTP 408 responses.

A extensão AddTransientHttpErrorPolicy pode ser usada em Startup.ConfigureServices.The AddTransientHttpErrorPolicy extension can be used within Startup.ConfigureServices. A extensão fornece acesso a um objeto PolicyBuilder configurado para tratar erros que representam uma possível falha transitória:The extension provides access to a PolicyBuilder object configured to handle errors representing a possible transient fault:

services.AddHttpClient<UnreliableEndpointCallerService>()
    .AddTransientHttpErrorPolicy(p => 
        p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

No código anterior, uma política WaitAndRetryAsync é definida.In the preceding code, a WaitAndRetryAsync policy is defined. As solicitações com falha são repetidas até três vezes com um atraso de 600 ms entre as tentativas.Failed requests are retried up to three times with a delay of 600 ms between attempts.

Selecionar políticas dinamicamenteDynamically select policies

Existem métodos de extensão adicionais que podem ser usados para adicionar manipuladores baseados no Polly.Additional extension methods exist which can be used to add Polly-based handlers. Uma dessas extensões é a AddPolicyHandler, que tem várias sobrecargas.One such extension is AddPolicyHandler, which has multiple overloads. Uma sobrecarga permite que a solicitação seja inspecionada durante a definição da política a ser aplicada:One overload allows the request to be inspected when defining which policy to apply:

var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
    .AddPolicyHandler(request => 
        request.Method == HttpMethod.Get ? timeout : longTimeout);

No código anterior, se a solicitação de saída é um HTTP GET, um tempo limite de 10 segundos é aplicado.In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. Para qualquer outro método HTTP, é usado um tempo limite de 30 segundos.For any other HTTP method, a 30-second timeout is used.

Adicionar vários manipuladores do PollyAdd multiple Polly handlers

É comum aninhar políticas do Polly para fornecer funcionalidade avançada:It's common to nest Polly policies to provide enhanced functionality:

services.AddHttpClient("multiplepolicies")
    .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

No exemplo anterior, dois manipuladores são adicionados.In the preceding example, two handlers are added. O primeiro usa a extensão AddTransientHttpErrorPolicy para adicionar uma política de repetição.The first uses the AddTransientHttpErrorPolicy extension to add a retry policy. As solicitações com falha são repetidas até três vezes.Failed requests are retried up to three times. A segunda chamada a AddTransientHttpErrorPolicy adiciona uma política de disjuntor.The second call to AddTransientHttpErrorPolicy adds a circuit breaker policy. As solicitações externas adicionais são bloqueadas por 30 segundos quando ocorrem cinco tentativas com falha consecutivamente.Further external requests are blocked for 30 seconds if five failed attempts occur sequentially. As políticas de disjuntor são políticas com estado.Circuit breaker policies are stateful. Todas as chamadas por meio desse cliente compartilham o mesmo estado do circuito.All calls through this client share the same circuit state.

Adicionar políticas do registro do PollyAdd policies from the Polly registry

Uma abordagem para gerenciar as políticas usadas com frequência é defini-las uma única vez e registrá-las em um PolicyRegistry.An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry. É fornecido um método de extensão que permite que um manipulador seja adicionado usando uma política do registro:An extension method is provided which allows a handler to be added using a policy from the registry:

var registry = services.AddPolicyRegistry();

registry.Add("regular", timeout);
registry.Add("long", longTimeout);

services.AddHttpClient("regulartimeouthandler")
    .AddPolicyHandlerFromRegistry("regular");

No código anterior, duas políticas são registradas quando PolicyRegistry é adicionado ao ServiceCollection.In the preceding code, two policies are registered when the PolicyRegistry is added to the ServiceCollection. Para usar uma política do registro, o método AddPolicyHandlerFromRegistry é usado, passando o nome da política a ser aplicada.To use a policy from the registry, the AddPolicyHandlerFromRegistry method is used, passing the name of the policy to apply.

Mais informações sobre o IHttpClientFactory e as integrações do Polly podem ser encontradas no Wiki do Polly.Further information about IHttpClientFactory and Polly integrations can be found on the Polly wiki.

HttpClient e gerenciamento de tempo de vidaHttpClient and lifetime management

Uma nova instância de HttpClient é chamada, sempre que CreateClient é chamado na IHttpClientFactory.A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. Há um HttpMessageHandler por cliente nomeado.There's an HttpMessageHandler per named client. O alocador gerencia a vida útil das instâncias do HttpMessageHandler.The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory cria pools com as instâncias de HttpMessageHandler criadas pelo alocador para reduzir o consumo de recursos.IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. Uma instância de HttpMessageHandler poderá ser reutilizada no pool, ao criar uma nova instância de HttpClient, se o respectivo tempo de vida não tiver expirado.An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn't expired.

O pooling de manipuladores é preferível porque normalmente cada manipulador gerencia as próprias conexões HTTP subjacentes.Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. Criar mais manipuladores do que o necessário pode resultar em atrasos de conexão.Creating more handlers than necessary can result in connection delays. Alguns manipuladores também mantêm as conexões abertas indefinidamente, o que pode impedir que o manipulador reaja a alterações de DNS.Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

O tempo de vida padrão do manipulador é de 2 minutos.The default handler lifetime is two minutes. O valor padrão pode ser substituído para cada cliente nomeado.The default value can be overridden on a per named client basis. Para substituí-lo, chame SetHandlerLifetime no IHttpClientBuilder que é retornado ao criar o cliente:To override it, call SetHandlerLifetime on the IHttpClientBuilder that is returned when creating the client:

services.AddHttpClient("extendedhandlerlifetime")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

Não é necessário descartar o cliente.Disposal of the client isn't required. O descarte cancela as solicitações de saída e garante que a determinada instância de HttpClient não seja usada depois de chamar Dispose.Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory rastreia e descarta recursos usados pelas instâncias de HttpClient.IHttpClientFactory tracks and disposes resources used by HttpClient instances. Geralmente, as instâncias de HttpClient podem ser tratadas como objetos do .NET e não exigem descarte.The HttpClient instances can generally be treated as .NET objects not requiring disposal.

Manter uma única instância de HttpClient ativa por uma longa duração é um padrão comum usado, antes do início de IHttpClientFactory.Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. Esse padrão se torna desnecessário após a migração para IHttpClientFactory.This pattern becomes unnecessary after migrating to IHttpClientFactory.

Alternativas para IHttpClientFactoryAlternatives to IHttpClientFactory

O uso de IHttpClientFactory em um aplicativo habilitado para DI é evitado:Using IHttpClientFactory in a DI-enabled app avoids:

  • Problemas de esgotamento de recursos por meio do pooling HttpMessageHandler instâncias.Resource exhaustion problems by pooling HttpMessageHandler instances.
  • Problemas de DNS obsoletos por ciclo HttpMessageHandler instâncias em intervalos regulares.Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

Há maneiras alternativas de resolver os problemas anteriores usando uma instância de SocketsHttpHandler de vida longa.There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • Crie uma instância do SocketsHttpHandler quando o aplicativo for iniciado e usado para a vida útil do aplicativo.Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configure PooledConnectionLifetime para um valor apropriado com base em tempos de atualização de DNS.Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Crie HttpClient instâncias usando new HttpClient(handler, disposeHandler: false) conforme necessário.Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

As abordagens anteriores resolvem os problemas de gerenciamento de recursos que IHttpClientFactory resolve de forma semelhante.The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • O SocketsHttpHandler compartilha conexões entre instâncias de HttpClient.The SocketsHttpHandler shares connections across HttpClient instances. Esse compartilhamento impede o esgotamento de soquete.This sharing prevents socket exhaustion.
  • O SocketsHttpHandler ciclos conexões de acordo com PooledConnectionLifetime para evitar problemas de DNS obsoletos.The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

CookiesCookies

As instâncias de HttpMessageHandler em pool resultam em CookieContainer objetos que estão sendo compartilhados.The pooled HttpMessageHandler instances results in CookieContainer objects being shared. O compartilhamento de objetos CookieContainer imprevistos geralmente resulta em código incorreto.Unanticipated CookieContainer object sharing often results in incorrect code. Para aplicativos que exigem cookies, considere o:For apps that require cookies, consider either:

  • Desabilitando a manipulação automática de cookiesDisabling automatic cookie handling
  • Evitando IHttpClientFactoryAvoiding IHttpClientFactory

Chame ConfigurePrimaryHttpMessageHandler para desabilitar o tratamento automático de cookies:Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

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

Registrando em logLogging

Os clientes criado pelo IHttpClientFactory registram mensagens de log para todas as solicitações.Clients created via IHttpClientFactory record log messages for all requests. Habilite o nível apropriado de informações na configuração de log para ver as mensagens de log padrão.Enable the appropriate information level in your logging configuration to see the default log messages. Os registros em log adicionais, como o registro em log dos cabeçalhos de solicitação, estão incluídos somente no nível de rastreamento.Additional logging, such as the logging of request headers, is only included at trace level.

A categoria de log usada para cada cliente inclui o nome do cliente.The log category used for each client includes the name of the client. Um cliente chamado MyNamedClient, por exemplo, registra mensagens com uma categoria de System.Net.Http.HttpClient.MyNamedClient.LogicalHandler.A client named MyNamedClient, for example, logs messages with a category of System.Net.Http.HttpClient.MyNamedClient.LogicalHandler. Mensagens sufixadas com LogicalHandler ocorrem fora do pipeline do manipulador de solicitações.Messages suffixed with LogicalHandler occur outside the request handler pipeline. Na solicitação, as mensagens são registradas em log antes que qualquer outro manipulador no pipeline a tenha processado.On the request, messages are logged before any other handlers in the pipeline have processed it. Na resposta, as mensagens são registradas depois que qualquer outro manipulador do pipeline tenha recebido a resposta.On the response, messages are logged after any other pipeline handlers have received the response.

O registro em log também ocorre dentro do pipeline do manipulador de solicitações.Logging also occurs inside the request handler pipeline. No caso do exemplo de MyNamedClient, essas mensagens são registradas em log na categoria de log System.Net.Http.HttpClient.MyNamedClient.ClientHandler.In the MyNamedClient example, those messages are logged against the log category System.Net.Http.HttpClient.MyNamedClient.ClientHandler. Para a solicitação, isso ocorre depois que todos os outros manipuladores são executados e imediatamente antes que a solicitação seja enviada pela rede.For the request, this occurs after all other handlers have run and immediately before the request is sent out on the network. Na resposta, esse registro em log inclui o estado da resposta antes que ela seja retornada por meio do pipeline do manipulador.On the response, this logging includes the state of the response before it passes back through the handler pipeline.

Habilitar o registro em log dentro e fora do pipeline permite a inspeção das alterações feitas por outros manipuladores do pipeline.Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. Isso pode incluir, por exemplo, alterações nos cabeçalhos de solicitação ou no código de status da resposta.This may include changes to request headers, for example, or to the response status code.

Incluir o nome do cliente na categoria de log permite a filtragem de log para clientes nomeados específicos, quando necessário.Including the name of the client in the log category enables log filtering for specific named clients where necessary.

Configurar o HttpMessageHandlerConfigure the HttpMessageHandler

Talvez seja necessário controlar a configuração do HttpMessageHandler interno usado por um cliente.It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

Um IHttpClientBuilder é retornado ao adicionar clientes nomeados ou com tipo.An IHttpClientBuilder is returned when adding named or typed clients. O método de extensão ConfigurePrimaryHttpMessageHandler pode ser usado para definir um representante.The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. O representante que é usado para criar e configurar o HttpMessageHandler primário usado pelo cliente:The delegate is used to create and configure the primary HttpMessageHandler used by that client:

services.AddHttpClient("configured-inner-handler")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            AllowAutoRedirect = false,
            UseDefaultCredentials = true
        };
    });

Usar IHttpClientFactory em um aplicativo de consoleUse IHttpClientFactory in a console app

Em um aplicativo de console, adicione as seguintes referências de pacote ao projeto:In a console app, add the following package references to the project:

No exemplo a seguir:In the following example:

  • IHttpClientFactory é registrado no contêiner de serviço do Host genérico.IHttpClientFactory is registered in the Generic Host's service container.
  • MyService cria uma instância de fábrica do cliente do serviço, que é usada para criar um HttpClient.MyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient é usado para recuperar uma página da Web.HttpClient is used to retrieve a webpage.
  • Main cria um escopo para executar o método GetPage do serviço e gravar os primeiros 500 caracteres do conteúdo da página da Web no console.Main creates a scope to execute the service's GetPage method and write the first 500 characters of the webpage content to the 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();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myService = services.GetRequiredService<IMyService>();
                var pageContent = await myService.GetPage();

                Console.WriteLine(pageContent.Substring(0, 500));
            }
            catch (Exception ex)
            {
                var logger = 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}";
            }
        }
    }
}

Recursos adicionaisAdditional resources

Por Glenn Condron, Ryan Nowak e Steve GordonBy Glenn Condron, Ryan Nowak, and Steve Gordon

É possível registrar e usar um IHttpClientFactory para configurar e criar instâncias de HttpClient em um aplicativo.An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. Ele oferece os seguintes benefícios:It offers the following benefits:

  • Fornece um local central para nomear e configurar instâncias lógicas de HttpClient.Provides a central location for naming and configuring logical HttpClient instances. Por exemplo, um cliente do github pode ser registrado e configurado para acessar o GitHub.For example, a github client can be registered and configured to access GitHub. Um cliente padrão pode ser registrado para outras finalidades.A default client can be registered for other purposes.
  • Codifica o conceito de middleware de saída por meio da delegação de manipuladores no HttpClient e fornece extensões para que o middleware baseado no Polly possa aproveitar esse recurso.Codifies the concept of outgoing middleware via delegating handlers in HttpClient and provides extensions for Polly-based middleware to take advantage of that.
  • Gerencia o pooling e o tempo de vida das instâncias de HttpClientMessageHandler subjacentes para evitar problemas de DNS comuns que ocorrem no gerenciamento manual de tempos de vida de HttpClient.Manages the pooling and lifetime of underlying HttpClientMessageHandler instances to avoid common DNS problems that occur when manually managing HttpClient lifetimes.
  • Adiciona uma experiência de registro em log configurável (via ILogger) para todas as solicitações enviadas por meio de clientes criados pelo alocador.Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

Exibir ou baixar código de exemplo (como baixar)View or download sample code (how to download)

{1>{2>Pré-requisitos<2}<1}Prerequisites

Os projetos direcionados ao .NET Framework exigem a instalação do pacote do NuGet Microsoft.Extensions.Http.Projects targeting .NET Framework require installation of the Microsoft.Extensions.Http NuGet package. Os projetos destinados ao .Net Core e a referência ao metapacote Microsoft.AspNetCore.App já incluem o pacote Microsoft.Extensions.Http.Projects that target .NET Core and reference the Microsoft.AspNetCore.App metapackage already include the Microsoft.Extensions.Http package.

Padrões de consumoConsumption patterns

Há várias maneiras de usar o IHttpClientFactory em um aplicativo:There are several ways IHttpClientFactory can be used in an app:

Nenhum deles é estritamente superiores ao outro.None of them are strictly superior to another. A melhor abordagem depende das restrições do aplicativo.The best approach depends upon the app's constraints.

Uso básicoBasic usage

O IHttpClientFactory pode ser registrado chamando o método de extensão AddHttpClient em IServiceCollection, dentro do método Startup.ConfigureServices.The IHttpClientFactory can be registered by calling the AddHttpClient extension method on the IServiceCollection, inside the Startup.ConfigureServices method.

services.AddHttpClient();

Depois de registrado, o código pode aceitar um IHttpClientFactory em qualquer lugar em que os serviços possam ser injetados com injeção de dependência.Once registered, code can accept an IHttpClientFactory anywhere services can be injected with dependency injection (DI). O IHttpClientFactory pode ser usado para criar uma instância de HttpClient:The IHttpClientFactory can be used to create an HttpClient instance:

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/aspnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            Branches = await response.Content
                .ReadAsAsync<IEnumerable<GitHubBranch>>();
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }                               
    }
}

Usar o IHttpClientFactory dessa forma é uma ótima maneira de refatorar um aplicativo existente.Using IHttpClientFactory in this fashion is a good way to refactor an existing app. Não há nenhum impacto na maneira em que o HttpClient é usado.It has no impact on the way HttpClient is used. Nos locais em que as instâncias de HttpClient são criadas no momento, substitua essas ocorrências por uma chamada a CreateClient.In places where HttpClient instances are currently created, replace those occurrences with a call to CreateClient.

Clientes nomeadosNamed clients

Quando um aplicativo exige vários usos distintos do HttpClient, cada um com uma configuração diferente, uma opção é usar clientes nomeados.If an app requires many distinct uses of HttpClient, each with a different configuration, an option is to use named clients. A configuração de um HttpClient nomeado pode ser especificada durante o registro em Startup.ConfigureServices.Configuration for a named HttpClient can be specified during registration in 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.In the preceding code, AddHttpClient is called, providing the name github. Esse cliente tem algumas configurações padrão aplicadas, ou seja, o endereço básico e dois cabeçalhos necessários para trabalhar com a API do GitHub.This client has some default configuration applied—namely the base address and two headers required to work with the GitHub API.

Toda vez que o CreateClient é chamado, uma nova instância de HttpClient é criada e a ação de configuração é chamada.Each time CreateClient is called, a new instance of HttpClient is created and the configuration action is called.

Para consumir um cliente nomeado, um parâmetro de cadeia de caracteres pode ser passado para CreateClient.To consume a named client, a string parameter can be passed to CreateClient. Especifique o nome do cliente a ser criado:Specify the name of the client to be created:

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/aspnet/AspNetCore.Docs/pulls");

        var client = _clientFactory.CreateClient("github");

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            PullRequests = await response.Content
                .ReadAsAsync<IEnumerable<GitHubPullRequest>>();
        }
        else
        {
            GetPullRequestsError = true;
            PullRequests = Array.Empty<GitHubPullRequest>();
        }
    }
}

No código anterior, a solicitação não precisa especificar um nome do host.In the preceding code, the request doesn't need to specify a hostname. Ela pode passar apenas o caminho, pois o endereço básico configurado para o cliente é usado.It can pass just the path, since the base address configured for the client is used.

Clientes com tipoTyped clients

Clientes com tipo:Typed clients:

  • Fornecem as mesmas funcionalidade que os clientes nomeados sem a necessidade de usar cadeias de caracteres como chaves.Provide the same capabilities as named clients without the need to use strings as keys.
  • Fornecem a ajuda do IntelliSense e do compilador durante o consumo de clientes.Provides IntelliSense and compiler help when consuming clients.
  • Fornecem um único local para configurar e interagir com um determinado HttpClient.Provide a single location to configure and interact with a particular HttpClient. Por exemplo, um único cliente com tipo pode ser usado para um único ponto de extremidade de back-end e encapsular toda a lógica que lida com esse ponto de extremidade.For example, a single typed client might be used for a single backend endpoint and encapsulate all logic dealing with that endpoint.
  • Funcionam com a DI e podem ser injetados no local necessário no aplicativo.Work with DI and can be injected where required in your app.

Um cliente tipado aceita um parâmetro HttpClient em seu construtor:A typed client accepts an HttpClient parameter in its constructor:

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/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");

        response.EnsureSuccessStatusCode();

        var result = await response.Content
            .ReadAsAsync<IEnumerable<GitHubIssue>>();

        return result;
    }
}

No código anterior, a configuração é movida para o cliente com tipo.In the preceding code, the configuration is moved into the typed client. O objeto HttpClient é exposto como uma propriedade pública.The HttpClient object is exposed as a public property. É possível definir métodos específicos da API que expõem a funcionalidade HttpClient.It's possible to define API-specific methods that expose HttpClient functionality. O método GetAspNetDocsIssues encapsula o código necessário para consultar e analisar os últimos problemas em aberto de um repositório GitHub.The GetAspNetDocsIssues method encapsulates the code needed to query for and parse out the latest open issues from a GitHub repository.

Para registrar um cliente com tipo, o método de extensão AddHttpClient genérico pode ser usado em Startup.ConfigureServices, especificando a classe do cliente com tipo:To register a typed client, the generic AddHttpClient extension method can be used within Startup.ConfigureServices, specifying the typed client class:

services.AddHttpClient<GitHubService>();

O cliente com tipo é registrado como transitório com a DI.The typed client is registered as transient with DI. O cliente com tipo pode ser injetado e consumido diretamente:The typed client can be injected and consumed directly:

public class TypedClientModel : PageModel
{
    private readonly GitHubService _gitHubService;

    public IEnumerable<GitHubIssue> LatestIssues { get; private set; }

    public bool HasIssue => LatestIssues.Any();

    public bool GetIssuesError { get; private set; }

    public TypedClientModel(GitHubService gitHubService)
    {
        _gitHubService = gitHubService;
    }

    public async Task OnGet()
    {
        try
        {
            LatestIssues = await _gitHubService.GetAspNetDocsIssues();
        }
        catch(HttpRequestException)
        {
            GetIssuesError = true;
            LatestIssues = Array.Empty<GitHubIssue>();
        }
    }
}

Se preferir, a configuração de um cliente com tipo poderá ser especificada durante o registro em Startup.ConfigureServices e não no construtor do cliente com tipo:If preferred, the configuration for a typed client can be specified during registration in Startup.ConfigureServices, rather than in the typed client's constructor:

services.AddHttpClient<RepoService>(c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

É possível encapsular totalmente o HttpClient dentro de um cliente com tipo.It's possible to entirely encapsulate the HttpClient within a typed client. Em vez de o expor como uma propriedade, é possível fornecer métodos públicos que chamam a instância de HttpClient internamente.Rather than exposing it as a property, public methods can be provided which call the HttpClient instance internally.

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.In the preceding code, the HttpClient is stored as a private field. Todo o acesso para fazer chamadas externas passa pelo método GetRepos.All access to make external calls goes through the GetRepos method.

Clientes geradosGenerated clients

O IHttpClientFactory pode ser usado em combinação com outras bibliotecas de terceiros, como a Refit.IHttpClientFactory can be used in combination with other third-party libraries such as Refit. Refit é uma biblioteca REST para .NET.Refit is a REST library for .NET. Ela converte APIs REST em interfaces dinâmicas.It converts REST APIs into live interfaces. Uma implementação da interface é gerada dinamicamente pelo RestService usando HttpClient para fazer as chamadas de HTTP externas.An implementation of the interface is generated dynamically by the RestService, using HttpClient to make the external HTTP calls.

Uma interface e uma resposta são definidas para representar a API externa e sua resposta:An interface and a reply are defined to represent the external API and its response:

public interface IHelloClient
{
    [Get("/helloworld")]
    Task<Reply> GetMessageAsync();
}

public class Reply
{
    public string Message { get; set; }
}

Um cliente com tipo pode ser adicionado usando a Refit para gerar a implementação:A typed client can be added, using Refit to generate the implementation:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("http://localhost:5000");
    })
    .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));

    services.AddMvc();
}

A interface definida pode ser consumida, quando necessário, com a implementação fornecida pela DI e pela Refit:The defined interface can be consumed where necessary, with the implementation provided by DI and 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ídaOutgoing request middleware

O HttpClient já tem o conceito de delegar manipuladores que podem ser vinculados para solicitações HTTP de saída.HttpClient already has the concept of delegating handlers that can be linked together for outgoing HTTP requests. O IHttpClientFactory facilita a definição dos manipuladores a serem aplicados a cada cliente nomeado.The IHttpClientFactory makes it easy to define the handlers to apply for each named client. Ele permite o registro e o encadeamento de vários manipuladores para criar um pipeline do middleware de solicitação saída.It supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. Cada um desses manipuladores é capaz de executar o trabalho antes e após a solicitação de saída.Each of these handlers is able to perform work before and after the outgoing request. Esse padrão é semelhante ao pipeline do middleware de entrada no ASP.NET Core.This pattern is similar to the inbound middleware pipeline in ASP.NET Core. O padrão fornece um mecanismo para gerenciar interesses paralelos em relação às solicitações HTTP, incluindo o armazenamento em cache, o tratamento de erro, a serialização e o registro em log.The pattern provides a mechanism to manage cross-cutting concerns around HTTP requests, including caching, error handling, serialization, and logging.

Para criar um manipulador, defina uma classe derivando-a de DelegatingHandler.To create a handler, define a class deriving from DelegatingHandler. Substitua o método SendAsync para executar o código antes de passar a solicitação para o próximo manipulador no pipeline:Override the SendAsync method to execute code before passing the request to the next handler in the 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.The preceding code defines a basic handler. Ele verifica se um cabeçalho X-API-KEY foi incluído na solicitação.It checks to see if an X-API-KEY header has been included on the request. Se o cabeçalho estiver ausente, isso poderá evitar a chamada de HTTP e retornar uma resposta adequada.If the header is missing, it can avoid the HTTP call and return a suitable response.

Durante o registro, um ou mais manipuladores podem ser adicionados à configuração para um HttpClient.During registration, one or more handlers can be added to the configuration for an HttpClient. Essa tarefa é realizada por meio de métodos de extensão no IHttpClientBuilder.This task is accomplished via extension methods on the IHttpClientBuilder.

services.AddTransient<ValidateHeaderHandler>();

services.AddHttpClient("externalservice", c =>
{
    // Assume this is an "external" service which requires an API KEY
    c.BaseAddress = new Uri("https://localhost:5000/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();

No código anterior, o ValidateHeaderHandler é registrado com a DI.In the preceding code, the ValidateHeaderHandler is registered with DI. O manipulador deve ser registrado na injeção de dependência como serviço temporário, mas nunca com escopo.The handler must be registered in DI as a transient service, never scoped. Se o manipulador estiver registrado como um serviço com escopo e algum serviço do qual ele depende for descartável:If the handler is registered as a scoped service and any services that the handler depends upon are disposable:

  • Os serviços do manipulador poderão ser descartados antes que ele saia do escopo.The handler's services could be disposed before the handler goes out of scope.
  • Os serviços de manipulador descartados farão com que o manipulador falhe.The disposed handler services causes the handler to fail.

Depois de registrado, o AddHttpMessageHandler poderá ser chamado, passando no tipo do manipulador.Once registered, AddHttpMessageHandler can be called, passing in the handler type.

Vários manipuladores podem ser registrados na ordem em que eles devem ser executados.Multiple handlers can be registered in the order that they should execute. Cada manipulador encapsula o próximo manipulador até que o HttpClientHandler final execute a solicitação:Each handler wraps the next handler until the final HttpClientHandler executes the request:

services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();

services.AddHttpClient("clientwithhandlers")
    // This handler is on the outside and called first during the 
    // request, last during the response.
    .AddHttpMessageHandler<SecureRequestHandler>()
    // This handler is on the inside, closest to the request being 
    // sent.
    .AddHttpMessageHandler<RequestDataHandler>();

Use uma das seguintes abordagens para compartilhar o estado por solicitação com os manipuladores de mensagens:Use one of the following approaches to share per-request state with message handlers:

  • Passe os dados pelo manipulador usando HttpRequestMessage.Properties.Pass data into the handler using HttpRequestMessage.Properties.
  • Use IHttpContextAccessor para acessar a solicitação atual.Use IHttpContextAccessor to access the current request.
  • Crie um objeto de armazenamento AsyncLocal personalizado para passar os dados.Create a custom AsyncLocal storage object to pass the data.

Usar manipuladores baseados no PollyUse Polly-based handlers

O IHttpClientFactory integra-se a uma biblioteca de terceiros popular chamada Polly.IHttpClientFactory integrates with a popular third-party library called Polly. O Polly é uma biblioteca abrangente de tratamento de falha transitória e de resiliência para .NET.Polly is a comprehensive resilience and transient fault-handling library for .NET. Ela permite que os desenvolvedores expressem políticas, como Repetição, Disjuntor, Tempo Limite, Isolamento de Bulkhead e Fallback de maneira fluente e thread-safe.It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Os métodos de extensão são fornecidos para habilitar o uso de políticas do Polly com instâncias de HttpClient configuradas.Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. As extensões do Polly:The Polly extensions:

  • Dão suporte à adição de manipuladores baseados no Polly aos clientes.Support adding Polly-based handlers to clients.
  • Podem ser usadas depois de instalar o pacote do NuGet Microsoft.Extensions.Http.Polly.Can be used after installing the Microsoft.Extensions.Http.Polly NuGet package. O pacote não está incluído na estrutura ASP.NET Core compartilhada.The package isn't included in the ASP.NET Core shared framework.

Tratar falhas transitóriasHandle transient faults

As falhas mais comuns ocorrem quando as chamadas HTTP externas são transitórias.Most common faults occur when external HTTP calls are transient. Um método de extensão conveniente chamado AddTransientHttpErrorPolicy é incluído para permitir que uma política seja definido para tratar erros transitórios.A convenient extension method called AddTransientHttpErrorPolicy is included which allows a policy to be defined to handle transient errors. As políticas configuradas com esse método de extensão tratam HttpRequestException, respostas HTTP 5xx e respostas HTTP 408.Policies configured with this extension method handle HttpRequestException, HTTP 5xx responses, and HTTP 408 responses.

A extensão AddTransientHttpErrorPolicy pode ser usada em Startup.ConfigureServices.The AddTransientHttpErrorPolicy extension can be used within Startup.ConfigureServices. A extensão fornece acesso a um objeto PolicyBuilder configurado para tratar erros que representam uma possível falha transitória:The extension provides access to a PolicyBuilder object configured to handle errors representing a possible transient fault:

services.AddHttpClient<UnreliableEndpointCallerService>()
    .AddTransientHttpErrorPolicy(p => 
        p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

No código anterior, uma política WaitAndRetryAsync é definida.In the preceding code, a WaitAndRetryAsync policy is defined. As solicitações com falha são repetidas até três vezes com um atraso de 600 ms entre as tentativas.Failed requests are retried up to three times with a delay of 600 ms between attempts.

Selecionar políticas dinamicamenteDynamically select policies

Existem métodos de extensão adicionais que podem ser usados para adicionar manipuladores baseados no Polly.Additional extension methods exist which can be used to add Polly-based handlers. Uma dessas extensões é a AddPolicyHandler, que tem várias sobrecargas.One such extension is AddPolicyHandler, which has multiple overloads. Uma sobrecarga permite que a solicitação seja inspecionada durante a definição da política a ser aplicada:One overload allows the request to be inspected when defining which policy to apply:

var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
    .AddPolicyHandler(request => 
        request.Method == HttpMethod.Get ? timeout : longTimeout);

No código anterior, se a solicitação de saída é um HTTP GET, um tempo limite de 10 segundos é aplicado.In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. Para qualquer outro método HTTP, é usado um tempo limite de 30 segundos.For any other HTTP method, a 30-second timeout is used.

Adicionar vários manipuladores do PollyAdd multiple Polly handlers

É comum aninhar políticas do Polly para fornecer funcionalidade avançada:It's common to nest Polly policies to provide enhanced functionality:

services.AddHttpClient("multiplepolicies")
    .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

No exemplo anterior, dois manipuladores são adicionados.In the preceding example, two handlers are added. O primeiro usa a extensão AddTransientHttpErrorPolicy para adicionar uma política de repetição.The first uses the AddTransientHttpErrorPolicy extension to add a retry policy. As solicitações com falha são repetidas até três vezes.Failed requests are retried up to three times. A segunda chamada a AddTransientHttpErrorPolicy adiciona uma política de disjuntor.The second call to AddTransientHttpErrorPolicy adds a circuit breaker policy. As solicitações externas adicionais são bloqueadas por 30 segundos quando ocorrem cinco tentativas com falha consecutivamente.Further external requests are blocked for 30 seconds if five failed attempts occur sequentially. As políticas de disjuntor são políticas com estado.Circuit breaker policies are stateful. Todas as chamadas por meio desse cliente compartilham o mesmo estado do circuito.All calls through this client share the same circuit state.

Adicionar políticas do registro do PollyAdd policies from the Polly registry

Uma abordagem para gerenciar as políticas usadas com frequência é defini-las uma única vez e registrá-las em um PolicyRegistry.An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry. É fornecido um método de extensão que permite que um manipulador seja adicionado usando uma política do registro:An extension method is provided which allows a handler to be added using a policy from the registry:

var registry = services.AddPolicyRegistry();

registry.Add("regular", timeout);
registry.Add("long", longTimeout);

services.AddHttpClient("regulartimeouthandler")
    .AddPolicyHandlerFromRegistry("regular");

No código anterior, duas políticas são registradas quando PolicyRegistry é adicionado ao ServiceCollection.In the preceding code, two policies are registered when the PolicyRegistry is added to the ServiceCollection. Para usar uma política do registro, o método AddPolicyHandlerFromRegistry é usado, passando o nome da política a ser aplicada.To use a policy from the registry, the AddPolicyHandlerFromRegistry method is used, passing the name of the policy to apply.

Mais informações sobre o IHttpClientFactory e as integrações do Polly podem ser encontradas no Wiki do Polly.Further information about IHttpClientFactory and Polly integrations can be found on the Polly wiki.

HttpClient e gerenciamento de tempo de vidaHttpClient and lifetime management

Uma nova instância de HttpClient é chamada, sempre que CreateClient é chamado na IHttpClientFactory.A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. Há um HttpMessageHandler por cliente nomeado.There's an HttpMessageHandler per named client. O alocador gerencia a vida útil das instâncias do HttpMessageHandler.The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory cria pools com as instâncias de HttpMessageHandler criadas pelo alocador para reduzir o consumo de recursos.IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. Uma instância de HttpMessageHandler poderá ser reutilizada no pool, ao criar uma nova instância de HttpClient, se o respectivo tempo de vida não tiver expirado.An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn't expired.

O pooling de manipuladores é preferível porque normalmente cada manipulador gerencia as próprias conexões HTTP subjacentes.Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. Criar mais manipuladores do que o necessário pode resultar em atrasos de conexão.Creating more handlers than necessary can result in connection delays. Alguns manipuladores também mantêm as conexões abertas indefinidamente, o que pode impedir que o manipulador reaja a alterações de DNS.Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

O tempo de vida padrão do manipulador é de 2 minutos.The default handler lifetime is two minutes. O valor padrão pode ser substituído para cada cliente nomeado.The default value can be overridden on a per named client basis. Para substituí-lo, chame SetHandlerLifetime no IHttpClientBuilder que é retornado ao criar o cliente:To override it, call SetHandlerLifetime on the IHttpClientBuilder that is returned when creating the client:

services.AddHttpClient("extendedhandlerlifetime")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

Não é necessário descartar o cliente.Disposal of the client isn't required. O descarte cancela as solicitações de saída e garante que a determinada instância de HttpClient não seja usada depois de chamar Dispose.Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory rastreia e descarta recursos usados pelas instâncias de HttpClient.IHttpClientFactory tracks and disposes resources used by HttpClient instances. Geralmente, as instâncias de HttpClient podem ser tratadas como objetos do .NET e não exigem descarte.The HttpClient instances can generally be treated as .NET objects not requiring disposal.

Manter uma única instância de HttpClient ativa por uma longa duração é um padrão comum usado, antes do início de IHttpClientFactory.Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. Esse padrão se torna desnecessário após a migração para IHttpClientFactory.This pattern becomes unnecessary after migrating to IHttpClientFactory.

Alternativas para IHttpClientFactoryAlternatives to IHttpClientFactory

O uso de IHttpClientFactory em um aplicativo habilitado para DI é evitado:Using IHttpClientFactory in a DI-enabled app avoids:

  • Problemas de esgotamento de recursos por meio do pooling HttpMessageHandler instâncias.Resource exhaustion problems by pooling HttpMessageHandler instances.
  • Problemas de DNS obsoletos por ciclo HttpMessageHandler instâncias em intervalos regulares.Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

Há maneiras alternativas de resolver os problemas anteriores usando uma instância de SocketsHttpHandler de vida longa.There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • Crie uma instância do SocketsHttpHandler quando o aplicativo for iniciado e usado para a vida útil do aplicativo.Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configure PooledConnectionLifetime para um valor apropriado com base em tempos de atualização de DNS.Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Crie HttpClient instâncias usando new HttpClient(handler, disposeHandler: false) conforme necessário.Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

As abordagens anteriores resolvem os problemas de gerenciamento de recursos que IHttpClientFactory resolve de forma semelhante.The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • O SocketsHttpHandler compartilha conexões entre instâncias de HttpClient.The SocketsHttpHandler shares connections across HttpClient instances. Esse compartilhamento impede o esgotamento de soquete.This sharing prevents socket exhaustion.
  • O SocketsHttpHandler ciclos conexões de acordo com PooledConnectionLifetime para evitar problemas de DNS obsoletos.The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

CookiesCookies

As instâncias de HttpMessageHandler em pool resultam em CookieContainer objetos que estão sendo compartilhados.The pooled HttpMessageHandler instances results in CookieContainer objects being shared. O compartilhamento de objetos CookieContainer imprevistos geralmente resulta em código incorreto.Unanticipated CookieContainer object sharing often results in incorrect code. Para aplicativos que exigem cookies, considere o:For apps that require cookies, consider either:

  • Desabilitando a manipulação automática de cookiesDisabling automatic cookie handling
  • Evitando IHttpClientFactoryAvoiding IHttpClientFactory

Chame ConfigurePrimaryHttpMessageHandler para desabilitar o tratamento automático de cookies:Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

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

Registrando em logLogging

Os clientes criado pelo IHttpClientFactory registram mensagens de log para todas as solicitações.Clients created via IHttpClientFactory record log messages for all requests. Habilite o nível apropriado de informações na configuração de log para ver as mensagens de log padrão.Enable the appropriate information level in your logging configuration to see the default log messages. Os registros em log adicionais, como o registro em log dos cabeçalhos de solicitação, estão incluídos somente no nível de rastreamento.Additional logging, such as the logging of request headers, is only included at trace level.

A categoria de log usada para cada cliente inclui o nome do cliente.The log category used for each client includes the name of the client. Um cliente chamado MyNamedClient, por exemplo, registra mensagens com uma categoria de System.Net.Http.HttpClient.MyNamedClient.LogicalHandler.A client named MyNamedClient, for example, logs messages with a category of System.Net.Http.HttpClient.MyNamedClient.LogicalHandler. Mensagens sufixadas com LogicalHandler ocorrem fora do pipeline do manipulador de solicitações.Messages suffixed with LogicalHandler occur outside the request handler pipeline. Na solicitação, as mensagens são registradas em log antes que qualquer outro manipulador no pipeline a tenha processado.On the request, messages are logged before any other handlers in the pipeline have processed it. Na resposta, as mensagens são registradas depois que qualquer outro manipulador do pipeline tenha recebido a resposta.On the response, messages are logged after any other pipeline handlers have received the response.

O registro em log também ocorre dentro do pipeline do manipulador de solicitações.Logging also occurs inside the request handler pipeline. No caso do exemplo de MyNamedClient, essas mensagens são registradas em log na categoria de log System.Net.Http.HttpClient.MyNamedClient.ClientHandler.In the MyNamedClient example, those messages are logged against the log category System.Net.Http.HttpClient.MyNamedClient.ClientHandler. Para a solicitação, isso ocorre depois que todos os outros manipuladores são executados e imediatamente antes que a solicitação seja enviada pela rede.For the request, this occurs after all other handlers have run and immediately before the request is sent out on the network. Na resposta, esse registro em log inclui o estado da resposta antes que ela seja retornada por meio do pipeline do manipulador.On the response, this logging includes the state of the response before it passes back through the handler pipeline.

Habilitar o registro em log dentro e fora do pipeline permite a inspeção das alterações feitas por outros manipuladores do pipeline.Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. Isso pode incluir, por exemplo, alterações nos cabeçalhos de solicitação ou no código de status da resposta.This may include changes to request headers, for example, or to the response status code.

Incluir o nome do cliente na categoria de log permite a filtragem de log para clientes nomeados específicos, quando necessário.Including the name of the client in the log category enables log filtering for specific named clients where necessary.

Configurar o HttpMessageHandlerConfigure the HttpMessageHandler

Talvez seja necessário controlar a configuração do HttpMessageHandler interno usado por um cliente.It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

Um IHttpClientBuilder é retornado ao adicionar clientes nomeados ou com tipo.An IHttpClientBuilder is returned when adding named or typed clients. O método de extensão ConfigurePrimaryHttpMessageHandler pode ser usado para definir um representante.The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. O representante que é usado para criar e configurar o HttpMessageHandler primário usado pelo cliente:The delegate is used to create and configure the primary HttpMessageHandler used by that client:

services.AddHttpClient("configured-inner-handler")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            AllowAutoRedirect = false,
            UseDefaultCredentials = true
        };
    });

Usar IHttpClientFactory em um aplicativo de consoleUse IHttpClientFactory in a console app

Em um aplicativo de console, adicione as seguintes referências de pacote ao projeto:In a console app, add the following package references to the project:

No exemplo a seguir:In the following example:

  • IHttpClientFactory é registrado no contêiner de serviço do Host genérico.IHttpClientFactory is registered in the Generic Host's service container.
  • MyService cria uma instância de fábrica do cliente do serviço, que é usada para criar um HttpClient.MyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient é usado para recuperar uma página da Web.HttpClient is used to retrieve a webpage.
  • Main cria um escopo para executar o método GetPage do serviço e gravar os primeiros 500 caracteres do conteúdo da página da Web no console.Main creates a scope to execute the service's GetPage method and write the first 500 characters of the webpage content to the 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();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myService = services.GetRequiredService<IMyService>();
                var pageContent = await myService.GetPage();

                Console.WriteLine(pageContent.Substring(0, 500));
            }
            catch (Exception ex)
            {
                var logger = 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}";
            }
        }
    }
}

Recursos adicionaisAdditional resources