Realización de solicitudes HTTP mediante IHttpClientFactory en ASP.NET CoreMake HTTP requests using IHttpClientFactory in ASP.NET Core

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

Se puede registrar y usar una interfaz IHttpClientFactory para crear y configurar instancias de HttpClient en una aplicación.An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. IHttpClientFactory ofrece las ventajas siguientes:IHttpClientFactory offers the following benefits:

  • Proporciona una ubicación central para denominar y configurar instancias de HttpClient lógicas.Provides a central location for naming and configuring logical HttpClient instances. Por ejemplo, se podría registrar un cliente github y configurarlo para acceder a GitHub.For example, a client named github could be registered and configured to access GitHub. Se puede registrar un cliente predeterminado para el acceso general.A default client can be registered for general access.
  • Codifica el concepto de middleware de salida a través de la delegación de controladores en HttpClient.Codifies the concept of outgoing middleware via delegating handlers in HttpClient. Proporciona extensiones para el middleware basado en Polly a fin de aprovechar los controladores de delegación en HttpClient.Provides extensions for Polly-based middleware to take advantage of delegating handlers in HttpClient.
  • Administra la agrupación y la duración de las instancias de HttpClientMessageHandler subyacentes.Manages the pooling and lifetime of underlying HttpClientMessageHandler instances. La administración automática evita los problemas comunes de DNS (Sistema de nombres de dominio) que se producen al administrar la duración de HttpClient de forma manual.Automatic management avoids common DNS (Domain Name System) problems that occur when manually managing HttpClient lifetimes.
  • Agrega una experiencia de registro configurable (a través de ILogger) en todas las solicitudes enviadas a través de los clientes creados por Factory.Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

Vea o descargue el código de ejemplo (cómo descargarlo).View or download sample code (how to download).

En el código de ejemplo de la versión este tema se usa System.Text.Json para deserializar el contenido JSON devuelto en las respuestas HTTP.The sample code in this topic version uses System.Text.Json to deserialize JSON content returned in HTTP responses. Para obtener ejemplos en los que se usan Json.NET y ReadAsAsync<T>, utilice el selector de versión para seleccionar una versión 2.x de este tema.For samples that use Json.NET and ReadAsAsync<T>, use the version selector to select a 2.x version of this topic.

Patrones de consumoConsumption patterns

IHttpClientFactory se puede usar de varias formas en una aplicación:There are several ways IHttpClientFactory can be used in an app:

El mejor enfoque depende de los requisitos de la aplicación.The best approach depends upon the app's requirements.

Uso básicoBasic usage

IHttpClientFactory se puede registrar mediante una llamada a 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.

Se puede solicitar una instancia de IHttpClientFactory mediante la inserción de dependencias (DI).An IHttpClientFactory can be requested using dependency injection (DI). En el código siguiente se usa IHttpClientFactory para crear una instancia 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>();
        }
    }
}

El uso de IHttpClientFactory como en el ejemplo anterior es una buena manera de refactorizar una aplicación existente.Using IHttpClientFactory like in the preceding example is a good way to refactor an existing app. No tiene efecto alguno en la forma de usar HttpClient.It has no impact on how HttpClient is used. En aquellos sitios de una aplicación existente en los que ya se hayan creado instancias de HttpClient, reemplace esas repeticiones por llamadas a CreateClient.In places where HttpClient instances are created in an existing app, replace those occurrences with calls to CreateClient.

Clientes con nombreNamed clients

Los clientes con nombre son una buena opción cuando:Named clients are a good choice when:

  • La aplicación requiere muchos usos distintos de HttpClient.The app requires many distinct uses of HttpClient.
  • Muchas instancias HttpClient de tienen otra configuración.Many HttpClients have different configuration.

La configuración de un objeto HttpClient con nombre se puede realizar durante la fase de registro en 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");
});

En el código anterior, el cliente está configurado con:In the preceding code the client is configured with:

  • La dirección base. https://api.github.com/.The base address https://api.github.com/.
  • Dos encabezados necesarios para trabajar con la API de GitHub.Two headers required to work with the GitHub API.

CreateClientCreateClient

Cada vez que se llama a CreateClient:Each time CreateClient is called:

  • Se crea una instancia de HttpClient.A new instance of HttpClient is created.
  • Se llama a la acción de configuración.The configuration action is called.

Para crear un cliente con nombre, pase su nombre a 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>();
        }
    }
}

En el código anterior, no es necesario especificar un nombre de host en la solicitud.In the preceding code, the request doesn't need to specify a hostname. El código puede pasar solo la ruta de acceso, ya que se usa la dirección base configurada del cliente.The code can pass just the path, since the base address configured for the client is used.

Clientes con tipoTyped clients

Clientes con tipo:Typed clients:

  • Proporcionan las mismas funciones que los clientes con nombre sin la necesidad de usar cadenas como claves.Provide the same capabilities as named clients without the need to use strings as keys.
  • Ofrecen ayuda relativa al compilador e IntelliSense al consumir clientes.Provides IntelliSense and compiler help when consuming clients.
  • Facilitan una sola ubicación para configurar un elemento HttpClient determinado e interactuar con él.Provide a single location to configure and interact with a particular HttpClient. Por ejemplo, es posible usar un solo cliente con tipo:For example, a single typed client might be used:
    • Para un único punto de conexión de back-end.For a single backend endpoint.
    • Para encapsular toda la lógica relacionada con el punto de conexión.To encapsulate all logic dealing with the endpoint.
  • Funcionan con la inserción de dependencias y se pueden insertar en la aplicación cuando sea necesario.Work with DI and can be injected where required in the app.

Un cliente con tipo acepta un parámetro HttpClient en su constructor: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);
    }
}

En el código anterior:In the preceding code:

  • La configuración se mueve al cliente con tipo.The configuration is moved into the typed client.
  • El objeto HttpClient se expone como una propiedad pública.The HttpClient object is exposed as a public property.

Se pueden crear métodos específicos de la API que exponen la funcionalidad de HttpClient.API-specific methods can be created that expose HttpClient functionality. Por ejemplo, el método GetAspNetDocsIssues encapsula el código para recuperar incidencias abiertas.For example, the GetAspNetDocsIssues method encapsulates code to retrieve open issues.

En el código siguiente se llama a AddHttpClient en Startup.ConfigureServices para registrar una clase de cliente con tipo:The following code calls AddHttpClient in Startup.ConfigureServices to register a typed client class:

services.AddHttpClient<GitHubService>();

El cliente con tipo se registra como transitorio con inserción con dependencias,The typed client is registered as transient with DI. y se puede insertar y consumir directamente: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>();
        }
    }
}

La configuración de un cliente con tipo se puede especificar durante su registro en Startup.ConfigureServices, en lugar de en su constructor: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");
});

HttpClient se puede encapsular dentro de un cliente con tipo.The HttpClient can be encapsulated within a typed client. En lugar de exponerlo como una propiedad, defina un método que llame a la instancia 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);
    }
}

En el código anterior, HttpClient se almacena en un campo privado.In the preceding code, the HttpClient is stored in a private field. El acceso a HttpClient se realiza mediante el método GetRepos público.Access to the HttpClient is by the public GetRepos method.

Clientes generadosGenerated clients

IHttpClientFactory se puede usar en combinación con bibliotecas de terceros, como Refit.IHttpClientFactory can be used in combination with third-party libraries such as Refit. Refit es una biblioteca de REST para .NETRefit is a REST library for .NET. que convierte las API de REST en interfaces en vivo.It converts REST APIs into live interfaces. Se genera una implementación de la interfaz dinámicamente por medio de RestService, usando HttpClient para realizar las llamadas HTTP externas.An implementation of the interface is generated dynamically by the RestService, using HttpClient to make the external HTTP calls.

Se define una interfaz y una respuesta para representar la API externa y su correspondiente respuesta: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; }
}

Un cliente con tipo se puede agregar usando Refit para generar la implementación: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();
}

La interfaz definida se puede usar cuando sea preciso con la implementación proporcionada por la inserción de dependencias y 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 solicitud salienteOutgoing request middleware

HttpClient tiene el concepto de controladores de delegación, que se pueden vincular entre sí para las solicitudes HTTP salientes.HttpClient has the concept of delegating handlers that can be linked together for outgoing HTTP requests. IHttpClientFactory:IHttpClientFactory:

  • simplifica la definición de controladores que se aplicarán por cada cliente con nombre.Simplifies defining the handlers to apply for each named client.

  • Admite el registro y encadenamiento de varios controladores para crear una canalización de middleware de solicitud saliente.Supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. Cada uno de estos controladores es capaz de realizar la tarea antes y después de la solicitud de salida.Each of these handlers is able to perform work before and after the outgoing request. Este patrón:This pattern:

    • Es similar a la canalización de middleware de entrada de ASP.NET Core.Is similar to the inbound middleware pipeline in ASP.NET Core.

    • Proporciona un mecanismo para administrar los intereses transversales relacionados con las solicitudes HTTP, como:Provides a mechanism to manage cross-cutting concerns around HTTP requests, such as:

      • el almacenamiento en cachécaching
      • el control de erroreserror handling
      • la serializaciónserialization
      • el registrologging

Para crear un controlador de delegación:To create a delegating handler:

  • Derívelo de DelegatingHandler.Derive from DelegatingHandler.
  • Reemplace SendAsync.Override SendAsync. Ejecute el código antes de pasar la solicitud al siguiente controlador de la canalización: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);
    }
}

El código anterior comprueba si el encabezado X-API-KEY está en la solicitud.The preceding code checks if the X-API-KEY header is in the request. Si falta X-API-KEY, se devuelve BadRequest.If X-API-KEY is missing, BadRequest is returned.

Se puede agregar más de un controlador a la configuración de una instancia de HttpClient con 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.

En el código anterior, ValidateHeaderHandler se ha registrado con inserción de dependencias.In the preceding code, the ValidateHeaderHandler is registered with DI. IHttpClientFactory crea un ámbito de inserción de dependencias independiente para cada controlador.The IHttpClientFactory creates a separate DI scope for each handler. Los controladores pueden depender de servicios de cualquier ámbito.Handlers can depend upon services of any scope. Los servicios de los que dependen los controladores se eliminan cuando se elimina el controlador.Services that handlers depend upon are disposed when the handler is disposed.

Una vez registrado, se puede llamar a AddHttpMessageHandler, pasando el tipo del controlador.Once registered, AddHttpMessageHandler can be called, passing in the type for the handler.

Se pueden registrar varios controladores en el orden en que deben ejecutarse.Multiple handlers can be registered in the order that they should execute. Cada controlador contiene el siguiente controlador hasta que el último HttpClientHandler ejecuta la solicitud: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 uno de los siguientes enfoques para compartir el estado por solicitud con controladores de mensajes:Use one of the following approaches to share per-request state with message handlers:

Usar controladores basados en PollyUse Polly-based handlers

IHttpClientFactory se integra con la biblioteca de terceros Polly.IHttpClientFactory integrates with the third-party library Polly. Polly es una biblioteca con capacidades de resistencia y control de errores transitorios para .NET.Polly is a comprehensive resilience and transient fault-handling library for .NET. Permite a los desarrolladores expresar directivas como, por ejemplo, de reintento, interruptor, tiempo de espera, aislamiento compartimentado y reserva de forma fluida y segura para los subprocesos.It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Se proporcionan métodos de extensión para hacer posible el uso de directivas de Polly con instancias de HttpClient configuradas.Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. Las extensiones de Polly permiten agregar controladores basados en Polly a los clientes.The Polly extensions support adding Polly-based handlers to clients. Polly requiere el paquete NuGet Microsoft.Extensions.Http.Polly.Polly requires the Microsoft.Extensions.Http.Polly NuGet package.

Control de errores transitoriosHandle transient faults

Los errores se suelen producir cuando las llamadas HTTP externas son transitorias.Faults typically occur when external HTTP calls are transient. AddTransientHttpErrorPolicy permite definir una directiva para controlar los errores transitorios.AddTransientHttpErrorPolicy allows a policy to be defined to handle transient errors. Las directivas configuradas con AddTransientHttpErrorPolicy controlan las respuestas siguientes:Policies configured with AddTransientHttpErrorPolicy handle the following responses:

AddTransientHttpErrorPolicy proporciona acceso a un objeto PolicyBuilder configurado para controlar los errores que representan un posible error transitorio: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.

En el código anterior, se define una directiva WaitAndRetryAsync.In the preceding code, a WaitAndRetryAsync policy is defined. Las solicitudes erróneas se reintentan hasta tres veces con un retardo de 600 ms entre intentos.Failed requests are retried up to three times with a delay of 600 ms between attempts.

Seleccionar directivas dinámicamenteDynamically select policies

Los métodos de extensión se proporcionan para agregar controladores basados en Polly, por ejemplo, AddPolicyHandler.Extension methods are provided to add Polly-based handlers, for example, AddPolicyHandler. La siguiente sobrecarga de AddPolicyHandler inspecciona la solicitud para decidir qué directiva se debe 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);

En el código anterior, si la solicitud GET saliente es del tipo HTTP, se aplica un tiempo de espera de 10 segundos.In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. En cualquier otro método HTTP, se usa un tiempo de espera de 30 segundos.For any other HTTP method, a 30-second timeout is used.

Agregar varios controladores de PollyAdd multiple Polly handlers

Es común anidar las directivas de Polly:It's common to nest Polly policies:

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

En el ejemplo anterior:In the preceding example:

  • Se agregan dos controladores.Two handlers are added.
  • El primer controlador usa AddTransientHttpErrorPolicy para agregar una directiva de reintentos.The first handler uses AddTransientHttpErrorPolicy to add a retry policy. Las solicitudes con error se reintentan hasta tres veces.Failed requests are retried up to three times.
  • La segunda llamada a AddTransientHttpErrorPolicy agrega una directiva de interruptor.The second AddTransientHttpErrorPolicy call adds a circuit breaker policy. Las solicitudes externas adicionales se bloquean durante 30 segundos si se producen cinco intentos con error seguidos.Further external requests are blocked for 30 seconds if 5 failed attempts occur sequentially. Las directivas de interruptor tienen estado.Circuit breaker policies are stateful. Así, todas las llamadas realizadas a través de este cliente comparten el mismo estado de circuito.All calls through this client share the same circuit state.

Agregar directivas desde el Registro de PollyAdd policies from the Polly registry

Una forma de administrar las directivas usadas habitualmente consiste en definirlas una vez y registrarlas con PolicyRegistry.An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry.

En el código siguiente: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 más información sobre IHttpClientFactory y las integraciones de Polly, vea la wiki de Polly.For more information on IHttpClientFactory and Polly integrations, see the Polly wiki.

HttpClient y administración de la duraciónHttpClient and lifetime management

Cada vez que se llama a CreateClient en IHttpClientFactory, se devuelve una nueva instancia de HttpClient.A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. Se crea un objeto HttpMessageHandler por cada cliente con nombre.An HttpMessageHandler is created per named client. La fábrica administra la duración de las instancias de HttpMessageHandler.The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory agrupa las instancias de HttpMessageHandler creadas por Factory para reducir el consumo de recursos.IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. Se puede reutilizar una instancia de HttpMessageHandler del grupo al crear una instancia de HttpClient si su duración aún no ha expirado.An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn't expired.

Se recomienda agrupar controladores porque cada uno de ellos normalmente administra sus propias conexiones HTTP subyacentes.Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. Crear más controladores de los necesarios puede provocar retrasos en la conexión.Creating more handlers than necessary can result in connection delays. Además, algunos controladores dejan las conexiones abiertas de forma indefinida, lo que puede impedir que el controlador reaccione ante los cambios de DNS (Sistema de nombres de dominio).Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS (Domain Name System) changes.

La duración de controlador predeterminada es dos minutos.The default handler lifetime is two minutes. El valor predeterminado se puede reemplazar de forma individual en cada cliente con nombre: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.

Normalmente, las instancias de HttpClient se pueden trata como objetos de .NET que no requieren eliminación.HttpClient instances can generally be treated as .NET objects not requiring disposal. ya que se cancelan las solicitudes salientes y la instancia de HttpClient determinada no se puede usar después de llamar a Dispose.Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory realiza un seguimiento y elimina los recursos que usan las instancias de HttpClient.IHttpClientFactory tracks and disposes resources used by HttpClient instances.

Mantener una sola instancia de HttpClient activa durante un período prolongado es un patrón común que se utiliza antes de la concepción de IHttpClientFactory.Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. Este patrón se convierte en innecesario tras la migración a IHttpClientFactory.This pattern becomes unnecessary after migrating to IHttpClientFactory.

Alternativas a IHttpClientFactoryAlternatives to IHttpClientFactory

El uso de IHttpClientFactory en una aplicación habilitada para la inserción de dependencias evita lo siguiente:Using IHttpClientFactory in a DI-enabled app avoids:

  • Problemas de agotamiento de recursos mediante la agrupación de instancias de HttpMessageHandler.Resource exhaustion problems by pooling HttpMessageHandler instances.
  • Problemas de DNS obsoletos al recorrer las instancias de HttpMessageHandler a intervalos regulares.Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

Existen formas alternativas de solucionar los problemas anteriores mediante una instancia de SocketsHttpHandler de larga duración.There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • Cree una instancia de SocketsHttpHandler al iniciar la aplicación y úsela para la vida útil de la aplicación.Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configure PooledConnectionLifetime con un valor adecuado en función de los tiempos de actualización de DNS.Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Cree instancias de HttpClient mediante new HttpClient(handler, disposeHandler: false) según sea necesario.Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

Los enfoques anteriores solucionan los problemas de administración de recursos que IHttpClientFactory resuelve de forma similar.The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • SocketsHttpHandler comparte las conexiones entre las instancias de HttpClient.The SocketsHttpHandler shares connections across HttpClient instances. Este uso compartido impide el agotamiento del socket.This sharing prevents socket exhaustion.
  • SocketsHttpHandler recorre las conexiones según PooledConnectionLifetime para evitar problemas de DNS obsoletos.The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

CookiesCookies

Las instancias de HttpMessageHandler agrupadas generan objetos CookieContainer que se comparten.The pooled HttpMessageHandler instances results in CookieContainer objects being shared. El uso compartido de objetos CookieContainer no previsto suele generar código incorrecto.Unanticipated CookieContainer object sharing often results in incorrect code. En el caso de las aplicaciones que requieren cookies, tenga en cuenta lo siguiente:For apps that require cookies, consider either:

  • Deshabilitar el control automático de las cookiesDisabling automatic cookie handling
  • Evitar IHttpClientFactoryAvoiding IHttpClientFactory

Llame a ConfigurePrimaryHttpMessageHandler para deshabilitar el control automático de cookies:Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

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

RegistroLogging

Los clientes que se han creado a través de IHttpClientFactory registran mensajes de registro de todas las solicitudes.Clients created via IHttpClientFactory record log messages for all requests. Habilite el nivel de información adecuado en la configuración del registro para ver los mensajes de registro predeterminados.Enable the appropriate information level in the logging configuration to see the default log messages. El registro de más información, como el registro de encabezados de solicitud, solo se incluye en el nivel de seguimiento.Additional logging, such as the logging of request headers, is only included at trace level.

La categoría de registro usada en cada cliente incluye el nombre del cliente.The log category used for each client includes the name of the client. Un cliente denominado MyNamedClient, por ejemplo, registra mensajes con una categoría 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". Los mensajes con el sufijo LogicalHandler se producen fuera de la canalización de controlador de la solicitud.Messages suffixed with LogicalHandler occur outside the request handler pipeline. En la solicitud, los mensajes se registran antes de que cualquier otro controlador de la canalización haya procesado la solicitud.On the request, messages are logged before any other handlers in the pipeline have processed it. En la respuesta, los mensajes se registran después de que cualquier otro controlador de la canalización haya recibido la respuesta.On the response, messages are logged after any other pipeline handlers have received the response.

El registro también se produce dentro de la canalización de controlador de la solicitud.Logging also occurs inside the request handler pipeline. En el ejemplo MyNamedClient, esos mensajes se registran con la categoría de registro "System.Net.Http.HttpClient.MyNamedClient.ClientHandler".In the MyNamedClient example, those messages are logged with the log category "System.Net.Http.HttpClient.MyNamedClient.ClientHandler". En la solicitud, esto tiene lugar después de que todos los demás controladores se hayan ejecutado y justo antes de que se envíe la solicitud.For the request, this occurs after all other handlers have run and immediately before the request is sent. En la respuesta, este registro incluye el estado de la respuesta antes de que vuelva a pasar por la canalización del controlador.On the response, this logging includes the state of the response before it passes back through the handler pipeline.

Al habilitar el registro tanto dentro como fuera de la canalización, se podrán inspeccionar los cambios realizados por otros controladores de la canalización.Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. Esto puede incluir cambios en los encabezados de solicitud o en el código de estado de la respuesta.This may include changes to request headers or to the response status code.

La inclusión del nombre del cliente en la categoría de registro permite filtrar el registro para clientes con nombre específicos.Including the name of the client in the log category enables log filtering for specific named clients.

Configurar HttpMessageHandlerConfigure the HttpMessageHandler

Puede que sea necesario controlar la configuración del elemento HttpMessageHandler interno usado por un cliente.It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

Se devuelve un IHttpClientBuilder cuando se agregan clientes con nombre o con tipo.An IHttpClientBuilder is returned when adding named or typed clients. Se puede usar el método de extensión ConfigurePrimaryHttpMessageHandler para definir un delegado.The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. Este delegado servirá para crear y configurar el elemento principal HttpMessageHandler que ese cliente usa: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.

Uso de IHttpClientFactory en una aplicación de consolaUse IHttpClientFactory in a console app

En una aplicación de consola, agregue las siguientes referencias de paquete al proyecto:In a console app, add the following package references to the project:

En el ejemplo siguiente:In the following example:

  • IHttpClientFactory está registrado en el contenedor de servicios del host genérico.IHttpClientFactory is registered in the Generic Host's service container.
  • MyService crea una instancia de generador de clientes a partir del servicio, que se usa para crear un elemento HttpClient.MyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient se utiliza para recuperar una página web.HttpClient is used to retrieve a webpage.
  • Main crea un ámbito para ejecutar el método GetPage del servicio y escribe los primeros 500 caracteres del contenido de la página web en la consola.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 adicionalesAdditional resources

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

Se puede registrar y usar una interfaz IHttpClientFactory para crear y configurar instancias de HttpClient en una aplicación.An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. Esto reporta las siguientes ventajas:It offers the following benefits:

  • Proporciona una ubicación central para denominar y configurar instancias de HttpClient lógicas.Provides a central location for naming and configuring logical HttpClient instances. Así, por ejemplo, se puede registrar y configurar un cliente github para tener acceso a GitHub.For example, a github client can be registered and configured to access GitHub. y, de igual modo, registrar otro cliente predeterminado para otros fines.A default client can be registered for other purposes.
  • Codifica el concepto de middleware saliente a través de controladores de delegación en HttpClient y proporciona extensiones para middleware basado en Polly para poder sacar partido de este.Codifies the concept of outgoing middleware via delegating handlers in HttpClient and provides extensions for Polly-based middleware to take advantage of that.
  • Administra la agrupación y duración de las instancias de HttpClientMessageHandler subyacentes para evitar los problemas de DNS que suelen producirse al administrar las duraciones de HttpClient manualmente.Manages the pooling and lifetime of underlying HttpClientMessageHandler instances to avoid common DNS problems that occur when manually managing HttpClient lifetimes.
  • Agrega una experiencia de registro configurable (a través de ILogger) en todas las solicitudes enviadas a través de los clientes creados por Factory.Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

Vea o descargue el código de ejemplo (cómo descargarlo)View or download sample code (how to download)

Patrones de consumoConsumption patterns

IHttpClientFactory se puede usar de varias formas en una aplicación:There are several ways IHttpClientFactory can be used in an app:

Ninguno de ellos es rigurosamente superior a otro,None of them are strictly superior to another. sino que el mejor método dependerá de las restricciones particulares de la aplicación.The best approach depends upon the app's constraints.

Uso básicoBasic usage

Se puede registrar un IHttpClientFactory llamando al método de extensión AddHttpClient en IServiceCollection, dentro del 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();

Una vez registrado, el código puede aceptar una interfaz IHttpClientFactory en cualquier parte donde se puedan insertar servicios por medio de la inserción de dependencias (DI).Once registered, code can accept an IHttpClientFactory anywhere services can be injected with dependency injection (DI). IHttpClientFactory se puede usar para crear una instancia 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>();
        }                               
    }
}

Cuando IHttpClientFactory se usa de este modo, constituye una excelente manera de refactorizar una aplicación existente.Using IHttpClientFactory in this fashion is a good way to refactor an existing app. No tiene efecto alguno en la forma en que HttpClient se usa.It has no impact on the way HttpClient is used. En aquellos sitios en los que ya se hayan creado instancias de HttpClient, reemplace esas apariciones por una llamada a CreateClient.In places where HttpClient instances are currently created, replace those occurrences with a call to CreateClient.

Clientes con nombreNamed clients

Si una aplicación necesita usar HttpClient de diversas maneras, cada una con una configuración diferente, una opción consiste en usar clientes con nombre.If an app requires many distinct uses of HttpClient, each with a different configuration, an option is to use named clients. La configuración de un HttpClient con nombre se puede realizar durante la fase de registro en 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");
});

En el código anterior, se llama a AddHttpClient usando el nombre github.In the preceding code, AddHttpClient is called, providing the name github. Este cliente tiene aplicadas algunas configuraciones predeterminadas, a saber, la dirección base y dos encabezados necesarios para trabajar con la API de GitHub.This client has some default configuration applied—namely the base address and two headers required to work with the GitHub API.

Cada vez que se llama a CreateClient, se crea otra instancia de HttpClient y se llama a la acción de configuración.Each time CreateClient is called, a new instance of HttpClient is created and the configuration action is called.

Para consumir un cliente con nombre, se puede pasar un parámetro de cadena a CreateClient.To consume a named client, a string parameter can be passed to CreateClient. Especifique el nombre del cliente que se va a crear: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>();
        }
    }
}

En el código anterior, no es necesario especificar un nombre de host en la solicitud.In the preceding code, the request doesn't need to specify a hostname. Basta con pasar solo la ruta de acceso, ya que se usa la dirección base configurada del cliente.It can pass just the path, since the base address configured for the client is used.

Clientes con tipoTyped clients

Clientes con tipo:Typed clients:

  • Proporcionan las mismas funciones que los clientes con nombre sin la necesidad de usar cadenas como claves.Provide the same capabilities as named clients without the need to use strings as keys.
  • Ofrecen ayuda relativa al compilador e IntelliSense al consumir clientes.Provides IntelliSense and compiler help when consuming clients.
  • Facilitan una sola ubicación para configurar un elemento HttpClient determinado e interactuar con él.Provide a single location to configure and interact with a particular HttpClient. Por ejemplo, el mismo cliente con tipo se puede usar para un punto de conexión back-end único y encapsular toda la lógica que se ocupa de ese punto de conexión.For example, a single typed client might be used for a single backend endpoint and encapsulate all logic dealing with that endpoint.
  • Funcionan con la inserción de dependencias y se pueden insertar en la aplicación cuando sea necesario.Work with DI and can be injected where required in your app.

Un cliente con tipo acepta un parámetro HttpClient en su constructor: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;
    }
}

En el código anterior, la configuración se mueve al cliente con tipo.In the preceding code, the configuration is moved into the typed client. El objeto HttpClient se expone como una propiedad pública.The HttpClient object is exposed as a public property. Se pueden definir métodos específicos de API que exponen la funcionalidad HttpClient.It's possible to define API-specific methods that expose HttpClient functionality. El método GetAspNetDocsIssues encapsula el código necesario para consultar y analizar los últimos problemas abiertos de un repositorio de GitHub.The GetAspNetDocsIssues method encapsulates the code needed to query for and parse out the latest open issues from a GitHub repository.

Para registrar un cliente con tipo, se puede usar el método de extensión genérico AddHttpClient dentro de Startup.ConfigureServices, especificando la clase del cliente con 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>();

El cliente con tipo se registra como transitorio con inserción con dependencias,The typed client is registered as transient with DI. y se puede insertar y consumir directamente: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>();
        }
    }
}

Si lo prefiere, la configuración de un cliente con nombre se puede especificar durante su registro en Startup.ConfigureServices, en lugar de en su constructor: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");
});

El HttpClient se puede encapsular completamente dentro de un cliente con nombre.It's possible to entirely encapsulate the HttpClient within a typed client. En lugar de exponerlo como una propiedad, se pueden proporcionar métodos públicos que llamen a la instancia 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;
    }
}

En el código anterior, el HttpClient se almacena como un campo privado.In the preceding code, the HttpClient is stored as a private field. Todo el acceso para realizar llamadas externas pasa por el método GetRepos.All access to make external calls goes through the GetRepos method.

Clientes generadosGenerated clients

IHttpClientFactory se puede usar en combinación con otras bibliotecas de terceros, como Refit.IHttpClientFactory can be used in combination with other third-party libraries such as Refit. Refit es una biblioteca de REST para .NETRefit is a REST library for .NET. que convierte las API de REST en interfaces en vivo.It converts REST APIs into live interfaces. Se genera una implementación de la interfaz dinámicamente por medio de RestService, usando HttpClient para realizar las llamadas HTTP externas.An implementation of the interface is generated dynamically by the RestService, using HttpClient to make the external HTTP calls.

Se define una interfaz y una respuesta para representar la API externa y su correspondiente respuesta: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; }
}

Un cliente con tipo se puede agregar usando Refit para generar la implementación: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();
}

La interfaz definida se puede usar cuando sea preciso con la implementación proporcionada por la inserción de dependencias y 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 solicitud salienteOutgoing request middleware

HttpClient ya posee el concepto de controladores de delegación, que se pueden vincular entre sí para las solicitudes HTTP salientes.HttpClient already has the concept of delegating handlers that can be linked together for outgoing HTTP requests. IHttpClientFactory permite definir fácilmente los controladores que se usarán en cada cliente con nombre.The IHttpClientFactory makes it easy to define the handlers to apply for each named client. Admite el registro y encadenamiento de varios controladores para crear una canalización de middleware de solicitud saliente.It supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. Cada uno de estos controladores es capaz de realizar la tarea antes y después de la solicitud de salida.Each of these handlers is able to perform work before and after the outgoing request. Este patrón es similar a la canalización de middleware de entrada de ASP.NET Core.This pattern is similar to the inbound middleware pipeline in ASP.NET Core. Dicho patrón proporciona un mecanismo para administrar cuestiones transversales relativas a las solicitudes HTTP, como el almacenamiento en caché, el control de errores, la serialización y el registro.The pattern provides a mechanism to manage cross-cutting concerns around HTTP requests, including caching, error handling, serialization, and logging.

Para crear un controlador, defina una clase que se derive de DelegatingHandler.To create a handler, define a class deriving from DelegatingHandler. Invalide el método SendAsync para ejecutar el código antes de pasar la solicitud al siguiente controlador de la canalización: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);
    }
}

El código anterior define un controlador básico.The preceding code defines a basic handler. Comprueba si se ha incluido un encabezado X-API-KEY en la solicitud.It checks to see if an X-API-KEY header has been included on the request. Si no está presente, puede evitar la llamada HTTP y devolver una respuesta adecuada.If the header is missing, it can avoid the HTTP call and return a suitable response.

Durante el registro, se pueden agregar uno o varios controladores a la configuración de una instancia de HttpClient.During registration, one or more handlers can be added to the configuration for an HttpClient. Esta tarea se realiza a través de métodos de extensión en 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>();

En el código anterior, ValidateHeaderHandler se ha registrado con inserción de dependencias.In the preceding code, the ValidateHeaderHandler is registered with DI. IHttpClientFactory crea un ámbito de inserción de dependencias independiente para cada controlador.The IHttpClientFactory creates a separate DI scope for each handler. Los controladores pueden depender de servicios de cualquier ámbito.Handlers are free to depend upon services of any scope. Los servicios de los que dependen los controladores se eliminan cuando se elimina el controlador.Services that handlers depend upon are disposed when the handler is disposed.

Una vez registrado, se puede llamar a AddHttpMessageHandler, pasando el tipo del controlador.Once registered, AddHttpMessageHandler can be called, passing in the type for the handler.

Se pueden registrar varios controladores en el orden en que deben ejecutarse.Multiple handlers can be registered in the order that they should execute. Cada controlador contiene el siguiente controlador hasta que el último HttpClientHandler ejecuta la solicitud: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 uno de los siguientes enfoques para compartir el estado por solicitud con controladores de mensajes:Use one of the following approaches to share per-request state with message handlers:

  • Pase datos al controlador usando HttpRequestMessage.Properties.Pass data into the handler using HttpRequestMessage.Properties.
  • Use IHttpContextAccessor para acceder a la solicitud actual.Use IHttpContextAccessor to access the current request.
  • Cree un objeto de almacenamiento AsyncLocal personalizado para pasar los datos.Create a custom AsyncLocal storage object to pass the data.

Usar controladores basados en PollyUse Polly-based handlers

IHttpClientFactory se integra con una biblioteca de terceros muy conocida denominada Polly.IHttpClientFactory integrates with a popular third-party library called Polly. Polly es una biblioteca con capacidades de resistencia y control de errores transitorios para .NET.Polly is a comprehensive resilience and transient fault-handling library for .NET. Permite a los desarrolladores expresar directivas como, por ejemplo, de reintento, interruptor, tiempo de espera, aislamiento compartimentado y reserva de forma fluida y segura para los subprocesos.It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Se proporcionan métodos de extensión para hacer posible el uso de directivas de Polly con instancias de HttpClient configuradas.Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. Las extensiones de Polly:The Polly extensions:

  • permiten agregar controladores basados en Polly a los clientes;Support adding Polly-based handlers to clients.
  • se pueden usar tras instalar el paquete NuGet Microsoft.Extensions.Http.Polly,Can be used after installing the Microsoft.Extensions.Http.Polly NuGet package. aunque este no está incluido en la plataforma compartida ASP.NET Core.The package isn't included in the ASP.NET Core shared framework.

Control de errores transitoriosHandle transient faults

Los errores más comunes se producen cuando las llamadas HTTP externas son transitorias.Most common faults occur when external HTTP calls are transient. Por ello, se incluye un método de extensión muy práctico denominado AddTransientHttpErrorPolicy, que permite definir una directiva para controlar los errores transitorios.A convenient extension method called AddTransientHttpErrorPolicy is included which allows a policy to be defined to handle transient errors. Las directivas que se configuran con este método de extensión controlan HttpRequestException, las respuestas HTTP 5xx y las respuestas HTTP 408.Policies configured with this extension method handle HttpRequestException, HTTP 5xx responses, and HTTP 408 responses.

La extensión AddTransientHttpErrorPolicy se puede usar en Startup.ConfigureServices.The AddTransientHttpErrorPolicy extension can be used within Startup.ConfigureServices. La extensión da acceso a un objeto PolicyBuilder, configurado para controlar los errores que pueden constituir un posible error transitorio: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)));

En el código anterior, se define una directiva WaitAndRetryAsync.In the preceding code, a WaitAndRetryAsync policy is defined. Las solicitudes erróneas se reintentan hasta tres veces con un retardo de 600 ms entre intentos.Failed requests are retried up to three times with a delay of 600 ms between attempts.

Seleccionar directivas dinámicamenteDynamically select policies

Existen más métodos de extensión que pueden servir para agregar controladores basados en Polly.Additional extension methods exist which can be used to add Polly-based handlers. Una de esas extensiones es AddPolicyHandler, que tiene varias sobrecargas.One such extension is AddPolicyHandler, which has multiple overloads. Una de esas sobrecargas permite inspeccionar la solicitud al dilucidar qué directiva aplicar: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);

En el código anterior, si la solicitud GET saliente es del tipo HTTP, se aplica un tiempo de espera de 10 segundos.In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. En cualquier otro método HTTP, se usa un tiempo de espera de 30 segundos.For any other HTTP method, a 30-second timeout is used.

Agregar varios controladores de PollyAdd multiple Polly handlers

Es habitual anidar directivas de Polly para proporcionar una funcionalidad mejorada: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)));

En el ejemplo anterior, se agregan dos controladores.In the preceding example, two handlers are added. En el primer ejemplo se usa la extensión AddTransientHttpErrorPolicy para agregar una directiva de reintento.The first uses the AddTransientHttpErrorPolicy extension to add a retry policy. Las solicitudes con error se reintentan hasta tres veces.Failed requests are retried up to three times. La segunda llamada a AddTransientHttpErrorPolicy agrega una directiva de interruptor.The second call to AddTransientHttpErrorPolicy adds a circuit breaker policy. Las solicitudes externas subsiguientes se bloquean durante 30 segundos si se producen cinco intentos infructuosos seguidos.Further external requests are blocked for 30 seconds if five failed attempts occur sequentially. Las directivas de interruptor tienen estado.Circuit breaker policies are stateful. Así, todas las llamadas realizadas a través de este cliente comparten el mismo estado de circuito.All calls through this client share the same circuit state.

Agregar directivas desde el Registro de PollyAdd policies from the Polly registry

Una forma de administrar las directivas usadas habitualmente consiste en definirlas una vez y registrarlas con PolicyRegistry.An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry. Se proporciona un método de extensión que permite agregar un controlador por medio de una directiva del 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");

En el código anterior, se registran dos directivas cuando se agrega PolicyRegistry a ServiceCollection.In the preceding code, two policies are registered when the PolicyRegistry is added to the ServiceCollection. Para usar una directiva del Registro, se usa el método AddPolicyHandlerFromRegistry pasando el nombre de la directiva que se va a aplicar.To use a policy from the registry, the AddPolicyHandlerFromRegistry method is used, passing the name of the policy to apply.

Encontrará más información sobre IHttpClientFactory y las integraciones de Polly en la wiki de Polly.Further information about IHttpClientFactory and Polly integrations can be found on the Polly wiki.

HttpClient y administración de la duraciónHttpClient and lifetime management

Cada vez que se llama a CreateClient en IHttpClientFactory, se devuelve una nueva instancia de HttpClient.A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. Hay un controlador HttpMessageHandler por cliente con nombre.There's an HttpMessageHandler per named client. La fábrica administra la duración de las instancias de HttpMessageHandler.The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory agrupa las instancias de HttpMessageHandler creadas por Factory para reducir el consumo de recursos.IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. Se puede reutilizar una instancia de HttpMessageHandler del grupo al crear una instancia de HttpClient si su duración aún no ha expirado.An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn't expired.

Se recomienda agrupar controladores porque cada uno de ellos normalmente administra sus propias conexiones HTTP subyacentes.Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. Crear más controladores de los necesarios puede provocar retrasos en la conexión.Creating more handlers than necessary can result in connection delays. Además, algunos controladores dejan las conexiones abiertas de forma indefinida, lo que puede ser un obstáculo a la hora de reaccionar ante los cambios de DNS.Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

La duración de controlador predeterminada es dos minutos.The default handler lifetime is two minutes. El valor predeterminado se puede reemplazar individualmente en cada cliente con nombre.The default value can be overridden on a per named client basis. Para ello, llame a SetHandlerLifetime en el IHttpClientBuilder que se devuelve cuando se crea el cliente:To override it, call SetHandlerLifetime on the IHttpClientBuilder that is returned when creating the client:

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

No hace falta eliminar el cliente,Disposal of the client isn't required. ya que se cancelan las solicitudes salientes y la instancia de HttpClient determinada no se puede usar después de llamar a Dispose.Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory realiza un seguimiento y elimina los recursos que usan las instancias de HttpClient.IHttpClientFactory tracks and disposes resources used by HttpClient instances. Normalmente, las instancias de HttpClient pueden tratarse como objetos de .NET que no requieren eliminación.The HttpClient instances can generally be treated as .NET objects not requiring disposal.

Mantener una sola instancia de HttpClient activa durante un período prolongado es un patrón común que se utiliza antes de la concepción de IHttpClientFactory.Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. Este patrón se convierte en innecesario tras la migración a IHttpClientFactory.This pattern becomes unnecessary after migrating to IHttpClientFactory.

Alternativas a IHttpClientFactoryAlternatives to IHttpClientFactory

El uso de IHttpClientFactory en una aplicación habilitada para la inserción de dependencias evita lo siguiente:Using IHttpClientFactory in a DI-enabled app avoids:

  • Problemas de agotamiento de recursos mediante la agrupación de instancias de HttpMessageHandler.Resource exhaustion problems by pooling HttpMessageHandler instances.
  • Problemas de DNS obsoletos al recorrer las instancias de HttpMessageHandler a intervalos regulares.Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

Existen formas alternativas de solucionar los problemas anteriores mediante una instancia de SocketsHttpHandler de larga duración.There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • Cree una instancia de SocketsHttpHandler al iniciar la aplicación y úsela para la vida útil de la aplicación.Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configure PooledConnectionLifetime con un valor adecuado en función de los tiempos de actualización de DNS.Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Cree instancias de HttpClient mediante new HttpClient(handler, disposeHandler: false) según sea necesario.Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

Los enfoques anteriores solucionan los problemas de administración de recursos que IHttpClientFactory resuelve de forma similar.The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • SocketsHttpHandler comparte las conexiones entre las instancias de HttpClient.The SocketsHttpHandler shares connections across HttpClient instances. Este uso compartido impide el agotamiento del socket.This sharing prevents socket exhaustion.
  • SocketsHttpHandler recorre las conexiones según PooledConnectionLifetime para evitar problemas de DNS obsoletos.The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

CookiesCookies

Las instancias de HttpMessageHandler agrupadas generan objetos CookieContainer que se comparten.The pooled HttpMessageHandler instances results in CookieContainer objects being shared. El uso compartido de objetos CookieContainer no previsto suele generar código incorrecto.Unanticipated CookieContainer object sharing often results in incorrect code. En el caso de las aplicaciones que requieren cookies, tenga en cuenta lo siguiente:For apps that require cookies, consider either:

  • Deshabilitar el control automático de las cookiesDisabling automatic cookie handling
  • Evitar IHttpClientFactoryAvoiding IHttpClientFactory

Llame a ConfigurePrimaryHttpMessageHandler para deshabilitar el control automático de cookies:Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

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

RegistroLogging

Los clientes que se han creado a través de IHttpClientFactory registran mensajes de registro de todas las solicitudes.Clients created via IHttpClientFactory record log messages for all requests. Habilite el nivel de información adecuado en la configuración del registro para ver los mensajes de registro predeterminados.Enable the appropriate information level in your logging configuration to see the default log messages. El registro de más información, como el registro de encabezados de solicitud, solo se incluye en el nivel de seguimiento.Additional logging, such as the logging of request headers, is only included at trace level.

La categoría de registro usada en cada cliente incluye el nombre del cliente.The log category used for each client includes the name of the client. Así, por ejemplo, un cliente llamado MyNamedClient registra mensajes con una categoría System.Net.Http.HttpClient.MyNamedClient.LogicalHandler.A client named MyNamedClient, for example, logs messages with a category of System.Net.Http.HttpClient.MyNamedClient.LogicalHandler. Los mensajes con el sufijo LogicalHandler se producen fuera de la canalización de controlador de la solicitud.Messages suffixed with LogicalHandler occur outside the request handler pipeline. En la solicitud, los mensajes se registran antes de que cualquier otro controlador de la canalización haya procesado la solicitud.On the request, messages are logged before any other handlers in the pipeline have processed it. En la respuesta, los mensajes se registran después de que cualquier otro controlador de la canalización haya recibido la respuesta.On the response, messages are logged after any other pipeline handlers have received the response.

El registro también se produce dentro de la canalización de controlador de la solicitud.Logging also occurs inside the request handler pipeline. En nuestro caso de ejemplo de MyNamedClient, esos mensajes se registran en la categoría de registro System.Net.Http.HttpClient.MyNamedClient.ClientHandler.In the MyNamedClient example, those messages are logged against the log category System.Net.Http.HttpClient.MyNamedClient.ClientHandler. En la solicitud, esto tiene lugar después de que todos los demás controladores se hayan ejecutado y justo antes de que la solicitud se envíe por la red.For the request, this occurs after all other handlers have run and immediately before the request is sent out on the network. En la respuesta, este registro incluye el estado de la respuesta antes de que vuelva a pasar por la canalización del controlador.On the response, this logging includes the state of the response before it passes back through the handler pipeline.

Al habilitar el registro tanto dentro como fuera de la canalización, se podrán inspeccionar los cambios realizados por otros controladores de la canalización.Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. Esto puede englobar cambios, por ejemplo, en los encabezados de solicitud o en el código de estado de la respuesta.This may include changes to request headers, for example, or to the response status code.

Si el nombre del cliente se incluye en la categoría de registro, dicho registro se podrá filtrar para encontrar clientes con nombre específicos cuando sea necesario.Including the name of the client in the log category enables log filtering for specific named clients where necessary.

Configurar HttpMessageHandlerConfigure the HttpMessageHandler

Puede que sea necesario controlar la configuración del elemento HttpMessageHandler interno usado por un cliente.It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

Se devuelve un IHttpClientBuilder cuando se agregan clientes con nombre o con tipo.An IHttpClientBuilder is returned when adding named or typed clients. Se puede usar el método de extensión ConfigurePrimaryHttpMessageHandler para definir un delegado.The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. Este delegado servirá para crear y configurar el elemento principal HttpMessageHandler que ese cliente usa: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
        };
    });

Uso de IHttpClientFactory en una aplicación de consolaUse IHttpClientFactory in a console app

En una aplicación de consola, agregue las siguientes referencias de paquete al proyecto:In a console app, add the following package references to the project:

En el ejemplo siguiente:In the following example:

  • IHttpClientFactory está registrado en el contenedor de servicios del host genérico.IHttpClientFactory is registered in the Generic Host's service container.
  • MyService crea una instancia de generador de clientes a partir del servicio, que se usa para crear un elemento HttpClient.MyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient se utiliza para recuperar una página web.HttpClient is used to retrieve a webpage.
  • Main crea un ámbito para ejecutar el método GetPage del servicio y escribe los primeros 500 caracteres del contenido de la página web en la consola.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 adicionalesAdditional resources

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

Se puede registrar y usar una interfaz IHttpClientFactory para crear y configurar instancias de HttpClient en una aplicación.An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. Esto reporta las siguientes ventajas:It offers the following benefits:

  • Proporciona una ubicación central para denominar y configurar instancias de HttpClient lógicas.Provides a central location for naming and configuring logical HttpClient instances. Así, por ejemplo, se puede registrar y configurar un cliente github para tener acceso a GitHub.For example, a github client can be registered and configured to access GitHub. y, de igual modo, registrar otro cliente predeterminado para otros fines.A default client can be registered for other purposes.
  • Codifica el concepto de middleware saliente a través de controladores de delegación en HttpClient y proporciona extensiones para middleware basado en Polly para poder sacar partido de este.Codifies the concept of outgoing middleware via delegating handlers in HttpClient and provides extensions for Polly-based middleware to take advantage of that.
  • Administra la agrupación y duración de las instancias de HttpClientMessageHandler subyacentes para evitar los problemas de DNS que suelen producirse al administrar las duraciones de HttpClient manualmente.Manages the pooling and lifetime of underlying HttpClientMessageHandler instances to avoid common DNS problems that occur when manually managing HttpClient lifetimes.
  • Agrega una experiencia de registro configurable (a través de ILogger) en todas las solicitudes enviadas a través de los clientes creados por Factory.Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

Vea o descargue el código de ejemplo (cómo descargarlo)View or download sample code (how to download)

Requisitos previosPrerequisites

Los proyectos para .NET Framework requieren instalar el paquete NuGet Microsoft.Extensions.Http.Projects targeting .NET Framework require installation of the Microsoft.Extensions.Http NuGet package. Los proyectos para .NET Core y que hagan referencia al metapaquete Microsoft.AspNetCore.App ya incluyen el paquete Microsoft.Extensions.Http.Projects that target .NET Core and reference the Microsoft.AspNetCore.App metapackage already include the Microsoft.Extensions.Http package.

Patrones de consumoConsumption patterns

IHttpClientFactory se puede usar de varias formas en una aplicación:There are several ways IHttpClientFactory can be used in an app:

Ninguno de ellos es rigurosamente superior a otro,None of them are strictly superior to another. sino que el mejor método dependerá de las restricciones particulares de la aplicación.The best approach depends upon the app's constraints.

Uso básicoBasic usage

Se puede registrar un IHttpClientFactory llamando al método de extensión AddHttpClient en IServiceCollection, dentro del 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();

Una vez registrado, el código puede aceptar una interfaz IHttpClientFactory en cualquier parte donde se puedan insertar servicios por medio de la inserción de dependencias (DI).Once registered, code can accept an IHttpClientFactory anywhere services can be injected with dependency injection (DI). IHttpClientFactory se puede usar para crear una instancia 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>();
        }                               
    }
}

Cuando IHttpClientFactory se usa de este modo, constituye una excelente manera de refactorizar una aplicación existente.Using IHttpClientFactory in this fashion is a good way to refactor an existing app. No tiene efecto alguno en la forma en que HttpClient se usa.It has no impact on the way HttpClient is used. En aquellos sitios en los que ya se hayan creado instancias de HttpClient, reemplace esas apariciones por una llamada a CreateClient.In places where HttpClient instances are currently created, replace those occurrences with a call to CreateClient.

Clientes con nombreNamed clients

Si una aplicación necesita usar HttpClient de diversas maneras, cada una con una configuración diferente, una opción consiste en usar clientes con nombre.If an app requires many distinct uses of HttpClient, each with a different configuration, an option is to use named clients. La configuración de un HttpClient con nombre se puede realizar durante la fase de registro en 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");
});

En el código anterior, se llama a AddHttpClient usando el nombre github.In the preceding code, AddHttpClient is called, providing the name github. Este cliente tiene aplicadas algunas configuraciones predeterminadas, a saber, la dirección base y dos encabezados necesarios para trabajar con la API de GitHub.This client has some default configuration applied—namely the base address and two headers required to work with the GitHub API.

Cada vez que se llama a CreateClient, se crea otra instancia de HttpClient y se llama a la acción de configuración.Each time CreateClient is called, a new instance of HttpClient is created and the configuration action is called.

Para consumir un cliente con nombre, se puede pasar un parámetro de cadena a CreateClient.To consume a named client, a string parameter can be passed to CreateClient. Especifique el nombre del cliente que se va a crear: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>();
        }
    }
}

En el código anterior, no es necesario especificar un nombre de host en la solicitud.In the preceding code, the request doesn't need to specify a hostname. Basta con pasar solo la ruta de acceso, ya que se usa la dirección base configurada del cliente.It can pass just the path, since the base address configured for the client is used.

Clientes con tipoTyped clients

Clientes con tipo:Typed clients:

  • Proporcionan las mismas funciones que los clientes con nombre sin la necesidad de usar cadenas como claves.Provide the same capabilities as named clients without the need to use strings as keys.
  • Ofrecen ayuda relativa al compilador e IntelliSense al consumir clientes.Provides IntelliSense and compiler help when consuming clients.
  • Facilitan una sola ubicación para configurar un elemento HttpClient determinado e interactuar con él.Provide a single location to configure and interact with a particular HttpClient. Por ejemplo, el mismo cliente con tipo se puede usar para un punto de conexión back-end único y encapsular toda la lógica que se ocupa de ese punto de conexión.For example, a single typed client might be used for a single backend endpoint and encapsulate all logic dealing with that endpoint.
  • Funcionan con la inserción de dependencias y se pueden insertar en la aplicación cuando sea necesario.Work with DI and can be injected where required in your app.

Un cliente con tipo acepta un parámetro HttpClient en su constructor: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;
    }
}

En el código anterior, la configuración se mueve al cliente con tipo.In the preceding code, the configuration is moved into the typed client. El objeto HttpClient se expone como una propiedad pública.The HttpClient object is exposed as a public property. Se pueden definir métodos específicos de API que exponen la funcionalidad HttpClient.It's possible to define API-specific methods that expose HttpClient functionality. El método GetAspNetDocsIssues encapsula el código necesario para consultar y analizar los últimos problemas abiertos de un repositorio de GitHub.The GetAspNetDocsIssues method encapsulates the code needed to query for and parse out the latest open issues from a GitHub repository.

Para registrar un cliente con tipo, se puede usar el método de extensión genérico AddHttpClient dentro de Startup.ConfigureServices, especificando la clase del cliente con 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>();

El cliente con tipo se registra como transitorio con inserción con dependencias,The typed client is registered as transient with DI. y se puede insertar y consumir directamente: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>();
        }
    }
}

Si lo prefiere, la configuración de un cliente con nombre se puede especificar durante su registro en Startup.ConfigureServices, en lugar de en su constructor: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");
});

El HttpClient se puede encapsular completamente dentro de un cliente con nombre.It's possible to entirely encapsulate the HttpClient within a typed client. En lugar de exponerlo como una propiedad, se pueden proporcionar métodos públicos que llamen a la instancia 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;
    }
}

En el código anterior, el HttpClient se almacena como un campo privado.In the preceding code, the HttpClient is stored as a private field. Todo el acceso para realizar llamadas externas pasa por el método GetRepos.All access to make external calls goes through the GetRepos method.

Clientes generadosGenerated clients

IHttpClientFactory se puede usar en combinación con otras bibliotecas de terceros, como Refit.IHttpClientFactory can be used in combination with other third-party libraries such as Refit. Refit es una biblioteca de REST para .NETRefit is a REST library for .NET. que convierte las API de REST en interfaces en vivo.It converts REST APIs into live interfaces. Se genera una implementación de la interfaz dinámicamente por medio de RestService, usando HttpClient para realizar las llamadas HTTP externas.An implementation of the interface is generated dynamically by the RestService, using HttpClient to make the external HTTP calls.

Se define una interfaz y una respuesta para representar la API externa y su correspondiente respuesta: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; }
}

Un cliente con tipo se puede agregar usando Refit para generar la implementación: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();
}

La interfaz definida se puede usar cuando sea preciso con la implementación proporcionada por la inserción de dependencias y 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 solicitud salienteOutgoing request middleware

HttpClient ya posee el concepto de controladores de delegación, que se pueden vincular entre sí para las solicitudes HTTP salientes.HttpClient already has the concept of delegating handlers that can be linked together for outgoing HTTP requests. IHttpClientFactory permite definir fácilmente los controladores que se usarán en cada cliente con nombre.The IHttpClientFactory makes it easy to define the handlers to apply for each named client. Admite el registro y encadenamiento de varios controladores para crear una canalización de middleware de solicitud saliente.It supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. Cada uno de estos controladores es capaz de realizar la tarea antes y después de la solicitud de salida.Each of these handlers is able to perform work before and after the outgoing request. Este patrón es similar a la canalización de middleware de entrada de ASP.NET Core.This pattern is similar to the inbound middleware pipeline in ASP.NET Core. Dicho patrón proporciona un mecanismo para administrar cuestiones transversales relativas a las solicitudes HTTP, como el almacenamiento en caché, el control de errores, la serialización y el registro.The pattern provides a mechanism to manage cross-cutting concerns around HTTP requests, including caching, error handling, serialization, and logging.

Para crear un controlador, defina una clase que se derive de DelegatingHandler.To create a handler, define a class deriving from DelegatingHandler. Invalide el método SendAsync para ejecutar el código antes de pasar la solicitud al siguiente controlador de la canalización: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);
    }
}

El código anterior define un controlador básico.The preceding code defines a basic handler. Comprueba si se ha incluido un encabezado X-API-KEY en la solicitud.It checks to see if an X-API-KEY header has been included on the request. Si no está presente, puede evitar la llamada HTTP y devolver una respuesta adecuada.If the header is missing, it can avoid the HTTP call and return a suitable response.

Durante el registro, se pueden agregar uno o varios controladores a la configuración de una instancia de HttpClient.During registration, one or more handlers can be added to the configuration for an HttpClient. Esta tarea se realiza a través de métodos de extensión en 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>();

En el código anterior, ValidateHeaderHandler se ha registrado con inserción de dependencias.In the preceding code, the ValidateHeaderHandler is registered with DI. El controlador debe estar registrado en la inserción de dependencias como servicio transitorio, nunca como servicio con ámbito.The handler must be registered in DI as a transient service, never scoped. Si el controlador se registra como servicio con ámbito y se pueden eliminar los servicios de los que depende el controlador:If the handler is registered as a scoped service and any services that the handler depends upon are disposable:

  • Los servicios del controlador se pueden eliminar antes de que el controlador deje de estar en el ámbito.The handler's services could be disposed before the handler goes out of scope.
  • Los servicios del controlador que se eliminen provocarán un error en él.The disposed handler services causes the handler to fail.

Una vez registrado, se puede llamar a AddHttpMessageHandler, con lo que se pasa el tipo de controlador.Once registered, AddHttpMessageHandler can be called, passing in the handler type.

Se pueden registrar varios controladores en el orden en que deben ejecutarse.Multiple handlers can be registered in the order that they should execute. Cada controlador contiene el siguiente controlador hasta que el último HttpClientHandler ejecuta la solicitud: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 uno de los siguientes enfoques para compartir el estado por solicitud con controladores de mensajes:Use one of the following approaches to share per-request state with message handlers:

  • Pase datos al controlador usando HttpRequestMessage.Properties.Pass data into the handler using HttpRequestMessage.Properties.
  • Use IHttpContextAccessor para acceder a la solicitud actual.Use IHttpContextAccessor to access the current request.
  • Cree un objeto de almacenamiento AsyncLocal personalizado para pasar los datos.Create a custom AsyncLocal storage object to pass the data.

Usar controladores basados en PollyUse Polly-based handlers

IHttpClientFactory se integra con una biblioteca de terceros muy conocida denominada Polly.IHttpClientFactory integrates with a popular third-party library called Polly. Polly es una biblioteca con capacidades de resistencia y control de errores transitorios para .NET.Polly is a comprehensive resilience and transient fault-handling library for .NET. Permite a los desarrolladores expresar directivas como, por ejemplo, de reintento, interruptor, tiempo de espera, aislamiento compartimentado y reserva de forma fluida y segura para los subprocesos.It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Se proporcionan métodos de extensión para hacer posible el uso de directivas de Polly con instancias de HttpClient configuradas.Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. Las extensiones de Polly:The Polly extensions:

  • permiten agregar controladores basados en Polly a los clientes;Support adding Polly-based handlers to clients.
  • se pueden usar tras instalar el paquete NuGet Microsoft.Extensions.Http.Polly,Can be used after installing the Microsoft.Extensions.Http.Polly NuGet package. aunque este no está incluido en la plataforma compartida ASP.NET Core.The package isn't included in the ASP.NET Core shared framework.

Control de errores transitoriosHandle transient faults

Los errores más comunes se producen cuando las llamadas HTTP externas son transitorias.Most common faults occur when external HTTP calls are transient. Por ello, se incluye un método de extensión muy práctico denominado AddTransientHttpErrorPolicy, que permite definir una directiva para controlar los errores transitorios.A convenient extension method called AddTransientHttpErrorPolicy is included which allows a policy to be defined to handle transient errors. Las directivas que se configuran con este método de extensión controlan HttpRequestException, las respuestas HTTP 5xx y las respuestas HTTP 408.Policies configured with this extension method handle HttpRequestException, HTTP 5xx responses, and HTTP 408 responses.

La extensión AddTransientHttpErrorPolicy se puede usar en Startup.ConfigureServices.The AddTransientHttpErrorPolicy extension can be used within Startup.ConfigureServices. La extensión da acceso a un objeto PolicyBuilder, configurado para controlar los errores que pueden constituir un posible error transitorio: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)));

En el código anterior, se define una directiva WaitAndRetryAsync.In the preceding code, a WaitAndRetryAsync policy is defined. Las solicitudes erróneas se reintentan hasta tres veces con un retardo de 600 ms entre intentos.Failed requests are retried up to three times with a delay of 600 ms between attempts.

Seleccionar directivas dinámicamenteDynamically select policies

Existen más métodos de extensión que pueden servir para agregar controladores basados en Polly.Additional extension methods exist which can be used to add Polly-based handlers. Una de esas extensiones es AddPolicyHandler, que tiene varias sobrecargas.One such extension is AddPolicyHandler, which has multiple overloads. Una de esas sobrecargas permite inspeccionar la solicitud al dilucidar qué directiva aplicar: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);

En el código anterior, si la solicitud GET saliente es del tipo HTTP, se aplica un tiempo de espera de 10 segundos.In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. En cualquier otro método HTTP, se usa un tiempo de espera de 30 segundos.For any other HTTP method, a 30-second timeout is used.

Agregar varios controladores de PollyAdd multiple Polly handlers

Es habitual anidar directivas de Polly para proporcionar una funcionalidad mejorada: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)));

En el ejemplo anterior, se agregan dos controladores.In the preceding example, two handlers are added. En el primer ejemplo se usa la extensión AddTransientHttpErrorPolicy para agregar una directiva de reintento.The first uses the AddTransientHttpErrorPolicy extension to add a retry policy. Las solicitudes con error se reintentan hasta tres veces.Failed requests are retried up to three times. La segunda llamada a AddTransientHttpErrorPolicy agrega una directiva de interruptor.The second call to AddTransientHttpErrorPolicy adds a circuit breaker policy. Las solicitudes externas subsiguientes se bloquean durante 30 segundos si se producen cinco intentos infructuosos seguidos.Further external requests are blocked for 30 seconds if five failed attempts occur sequentially. Las directivas de interruptor tienen estado.Circuit breaker policies are stateful. Así, todas las llamadas realizadas a través de este cliente comparten el mismo estado de circuito.All calls through this client share the same circuit state.

Agregar directivas desde el Registro de PollyAdd policies from the Polly registry

Una forma de administrar las directivas usadas habitualmente consiste en definirlas una vez y registrarlas con PolicyRegistry.An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry. Se proporciona un método de extensión que permite agregar un controlador por medio de una directiva del 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");

En el código anterior, se registran dos directivas cuando se agrega PolicyRegistry a ServiceCollection.In the preceding code, two policies are registered when the PolicyRegistry is added to the ServiceCollection. Para usar una directiva del Registro, se usa el método AddPolicyHandlerFromRegistry pasando el nombre de la directiva que se va a aplicar.To use a policy from the registry, the AddPolicyHandlerFromRegistry method is used, passing the name of the policy to apply.

Encontrará más información sobre IHttpClientFactory y las integraciones de Polly en la wiki de Polly.Further information about IHttpClientFactory and Polly integrations can be found on the Polly wiki.

HttpClient y administración de la duraciónHttpClient and lifetime management

Cada vez que se llama a CreateClient en IHttpClientFactory, se devuelve una nueva instancia de HttpClient.A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. Hay un controlador HttpMessageHandler por cliente con nombre.There's an HttpMessageHandler per named client. La fábrica administra la duración de las instancias de HttpMessageHandler.The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory agrupa las instancias de HttpMessageHandler creadas por Factory para reducir el consumo de recursos.IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. Se puede reutilizar una instancia de HttpMessageHandler del grupo al crear una instancia de HttpClient si su duración aún no ha expirado.An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn't expired.

Se recomienda agrupar controladores porque cada uno de ellos normalmente administra sus propias conexiones HTTP subyacentes.Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. Crear más controladores de los necesarios puede provocar retrasos en la conexión.Creating more handlers than necessary can result in connection delays. Además, algunos controladores dejan las conexiones abiertas de forma indefinida, lo que puede ser un obstáculo a la hora de reaccionar ante los cambios de DNS.Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

La duración de controlador predeterminada es dos minutos.The default handler lifetime is two minutes. El valor predeterminado se puede reemplazar individualmente en cada cliente con nombre.The default value can be overridden on a per named client basis. Para ello, llame a SetHandlerLifetime en el IHttpClientBuilder que se devuelve cuando se crea el cliente:To override it, call SetHandlerLifetime on the IHttpClientBuilder that is returned when creating the client:

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

No hace falta eliminar el cliente,Disposal of the client isn't required. ya que se cancelan las solicitudes salientes y la instancia de HttpClient determinada no se puede usar después de llamar a Dispose.Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory realiza un seguimiento y elimina los recursos que usan las instancias de HttpClient.IHttpClientFactory tracks and disposes resources used by HttpClient instances. Normalmente, las instancias de HttpClient pueden tratarse como objetos de .NET que no requieren eliminación.The HttpClient instances can generally be treated as .NET objects not requiring disposal.

Mantener una sola instancia de HttpClient activa durante un período prolongado es un patrón común que se utiliza antes de la concepción de IHttpClientFactory.Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. Este patrón se convierte en innecesario tras la migración a IHttpClientFactory.This pattern becomes unnecessary after migrating to IHttpClientFactory.

Alternativas a IHttpClientFactoryAlternatives to IHttpClientFactory

El uso de IHttpClientFactory en una aplicación habilitada para la inserción de dependencias evita lo siguiente:Using IHttpClientFactory in a DI-enabled app avoids:

  • Problemas de agotamiento de recursos mediante la agrupación de instancias de HttpMessageHandler.Resource exhaustion problems by pooling HttpMessageHandler instances.
  • Problemas de DNS obsoletos al recorrer las instancias de HttpMessageHandler a intervalos regulares.Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

Existen formas alternativas de solucionar los problemas anteriores mediante una instancia de SocketsHttpHandler de larga duración.There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • Cree una instancia de SocketsHttpHandler al iniciar la aplicación y úsela para la vida útil de la aplicación.Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configure PooledConnectionLifetime con un valor adecuado en función de los tiempos de actualización de DNS.Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Cree instancias de HttpClient mediante new HttpClient(handler, disposeHandler: false) según sea necesario.Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

Los enfoques anteriores solucionan los problemas de administración de recursos que IHttpClientFactory resuelve de forma similar.The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • SocketsHttpHandler comparte las conexiones entre las instancias de HttpClient.The SocketsHttpHandler shares connections across HttpClient instances. Este uso compartido impide el agotamiento del socket.This sharing prevents socket exhaustion.
  • SocketsHttpHandler recorre las conexiones según PooledConnectionLifetime para evitar problemas de DNS obsoletos.The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

CookiesCookies

Las instancias de HttpMessageHandler agrupadas generan objetos CookieContainer que se comparten.The pooled HttpMessageHandler instances results in CookieContainer objects being shared. El uso compartido de objetos CookieContainer no previsto suele generar código incorrecto.Unanticipated CookieContainer object sharing often results in incorrect code. En el caso de las aplicaciones que requieren cookies, tenga en cuenta lo siguiente:For apps that require cookies, consider either:

  • Deshabilitar el control automático de las cookiesDisabling automatic cookie handling
  • Evitar IHttpClientFactoryAvoiding IHttpClientFactory

Llame a ConfigurePrimaryHttpMessageHandler para deshabilitar el control automático de cookies:Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

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

RegistroLogging

Los clientes que se han creado a través de IHttpClientFactory registran mensajes de registro de todas las solicitudes.Clients created via IHttpClientFactory record log messages for all requests. Habilite el nivel de información adecuado en la configuración del registro para ver los mensajes de registro predeterminados.Enable the appropriate information level in your logging configuration to see the default log messages. El registro de más información, como el registro de encabezados de solicitud, solo se incluye en el nivel de seguimiento.Additional logging, such as the logging of request headers, is only included at trace level.

La categoría de registro usada en cada cliente incluye el nombre del cliente.The log category used for each client includes the name of the client. Así, por ejemplo, un cliente llamado MyNamedClient registra mensajes con una categoría System.Net.Http.HttpClient.MyNamedClient.LogicalHandler.A client named MyNamedClient, for example, logs messages with a category of System.Net.Http.HttpClient.MyNamedClient.LogicalHandler. Los mensajes con el sufijo LogicalHandler se producen fuera de la canalización de controlador de la solicitud.Messages suffixed with LogicalHandler occur outside the request handler pipeline. En la solicitud, los mensajes se registran antes de que cualquier otro controlador de la canalización haya procesado la solicitud.On the request, messages are logged before any other handlers in the pipeline have processed it. En la respuesta, los mensajes se registran después de que cualquier otro controlador de la canalización haya recibido la respuesta.On the response, messages are logged after any other pipeline handlers have received the response.

El registro también se produce dentro de la canalización de controlador de la solicitud.Logging also occurs inside the request handler pipeline. En nuestro caso de ejemplo de MyNamedClient, esos mensajes se registran en la categoría de registro System.Net.Http.HttpClient.MyNamedClient.ClientHandler.In the MyNamedClient example, those messages are logged against the log category System.Net.Http.HttpClient.MyNamedClient.ClientHandler. En la solicitud, esto tiene lugar después de que todos los demás controladores se hayan ejecutado y justo antes de que la solicitud se envíe por la red.For the request, this occurs after all other handlers have run and immediately before the request is sent out on the network. En la respuesta, este registro incluye el estado de la respuesta antes de que vuelva a pasar por la canalización del controlador.On the response, this logging includes the state of the response before it passes back through the handler pipeline.

Al habilitar el registro tanto dentro como fuera de la canalización, se podrán inspeccionar los cambios realizados por otros controladores de la canalización.Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. Esto puede englobar cambios, por ejemplo, en los encabezados de solicitud o en el código de estado de la respuesta.This may include changes to request headers, for example, or to the response status code.

Si el nombre del cliente se incluye en la categoría de registro, dicho registro se podrá filtrar para encontrar clientes con nombre específicos cuando sea necesario.Including the name of the client in the log category enables log filtering for specific named clients where necessary.

Configurar HttpMessageHandlerConfigure the HttpMessageHandler

Puede que sea necesario controlar la configuración del elemento HttpMessageHandler interno usado por un cliente.It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

Se devuelve un IHttpClientBuilder cuando se agregan clientes con nombre o con tipo.An IHttpClientBuilder is returned when adding named or typed clients. Se puede usar el método de extensión ConfigurePrimaryHttpMessageHandler para definir un delegado.The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. Este delegado servirá para crear y configurar el elemento principal HttpMessageHandler que ese cliente usa: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
        };
    });

Uso de IHttpClientFactory en una aplicación de consolaUse IHttpClientFactory in a console app

En una aplicación de consola, agregue las siguientes referencias de paquete al proyecto:In a console app, add the following package references to the project:

En el ejemplo siguiente:In the following example:

  • IHttpClientFactory está registrado en el contenedor de servicios del host genérico.IHttpClientFactory is registered in the Generic Host's service container.
  • MyService crea una instancia de generador de clientes a partir del servicio, que se usa para crear un elemento HttpClient.MyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient se utiliza para recuperar una página web.HttpClient is used to retrieve a webpage.
  • Main crea un ámbito para ejecutar el método GetPage del servicio y escribe los primeros 500 caracteres del contenido de la página web en la consola.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 adicionalesAdditional resources