Realización de solicitudes HTTP mediante IHttpClientFactory en ASP.NET Core
Nota:
Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Advertencia
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulta la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulta la versión .NET 8 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión de .NET 9 de este artículo.
Por Kirk Larkin, Steve Gordon, Glenn Condron y Ryan Nowak.
Se puede registrar y usar una interfaz IHttpClientFactory para crear y configurar instancias de HttpClient en una aplicación. IHttpClientFactory
ofrece las ventajas siguientes:
- Proporciona una ubicación central para denominar y configurar instancias de
HttpClient
lógicas. Por ejemplo, se podría registrar un cliente llamado github y configurarlo para acceder a GitHub. Se puede registrar un cliente predeterminado para el acceso general. - Codifica el concepto de middleware de salida a través de la delegación de controladores en
HttpClient
. Proporciona extensiones para el middleware basado en Polly a fin de aprovechar los controladores de delegación enHttpClient
. - Administra la agrupación y la duración de las instancias de
HttpClientMessageHandler
subyacentes. La administración automática evita los problemas comunes de DNS (Sistema de nombres de dominio) que se producen al administrar la duración deHttpClient
de forma manual. - 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.
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. 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.
Patrones de consumo
IHttpClientFactory
se puede usar de varias formas en una aplicación:
El mejor enfoque depende de los requisitos de la aplicación.
Uso básico
Registre IHttpClientFactory
mediante una llamada a AddHttpClient
en Program.cs
:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient();
Se puede solicitar una instancia de IHttpClientFactory
mediante la inserción de dependencias (DI). En el código siguiente se usa IHttpClientFactory
para crear una instancia de HttpClient
:
public class BasicModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public BasicModel(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ HeaderNames.Accept, "application/vnd.github.v3+json" },
{ HeaderNames.UserAgent, "HttpRequestsSample" }
}
};
var httpClient = _httpClientFactory.CreateClient();
var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
El uso de IHttpClientFactory
como en el ejemplo anterior es una buena manera de refactorizar una aplicación existente. No tiene efecto alguno en la forma de usar HttpClient
. 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.
Clientes con nombre
Los clientes con nombre son una buena opción cuando:
- La aplicación requiere muchos usos distintos de
HttpClient
. - Muchas instancias
HttpClient
de tienen otra configuración.
Especifique la configuración de un objeto HttpClient
con nombre durante su registro en Program.cs
:
builder.Services.AddHttpClient("GitHub", httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});
En el código anterior, el cliente está configurado con:
- La dirección base.
https://api.github.com/
. - Dos encabezados necesarios para trabajar con la API de GitHub.
CreateClient
Cada vez que se llama a CreateClient:
- Se crea una instancia de
HttpClient
. - Se llama a la acción de configuración.
Para crear un cliente con nombre, pase su nombre a CreateClient
:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public NamedClientModel(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
var httpClient = _httpClientFactory.CreateClient("GitHub");
var httpResponseMessage = await httpClient.GetAsync(
"repos/dotnet/AspNetCore.Docs/branches");
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
En el código anterior, no es necesario especificar un nombre de host en la solicitud. El código puede pasar solo la ruta de acceso, ya que se usa la dirección base configurada del cliente.
Clientes con tipo
Clientes con tipo:
- Proporcionan las mismas funciones que los clientes con nombre sin la necesidad de usar cadenas como claves.
- Ofrecen ayuda relativa al compilador e IntelliSense al consumir clientes.
- Facilitan una sola ubicación para configurar un elemento
HttpClient
determinado e interactuar con él. Por ejemplo, es posible usar un solo cliente con tipo:- Para un único punto de conexión de back-end.
- Para encapsular toda la lógica relacionada con el punto de conexión.
- Funcionan con la inserción de dependencias y se pueden insertar en la aplicación cuando sea necesario.
Un cliente con tipo acepta un parámetro HttpClient
en su constructor:
public class GitHubService
{
private readonly HttpClient _httpClient;
public GitHubService(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
}
public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync() =>
await _httpClient.GetFromJsonAsync<IEnumerable<GitHubBranch>>(
"repos/dotnet/AspNetCore.Docs/branches");
}
En el código anterior:
- La configuración se mueve al cliente con tipo.
- La instancia de
HttpClient
proporcionada se almacena como un campo privado.
Se pueden crear métodos específicos de la API que exponen la funcionalidad de HttpClient
. Por ejemplo, el método GetAspNetCoreDocsBranches
encapsula el código para recuperar las ramas de GitHub de documentos.
En el código siguiente se llama a AddHttpClient en Program.cs
para registrar una clase de cliente GitHubService
con tipo:
builder.Services.AddHttpClient<GitHubService>();
El cliente con tipo se registra como transitorio con inserción con dependencias, En el código anterior, AddHttpClient
registra GitHubService
como servicio transitorio. Este registro usa un Factory Method para:
- Crea una instancia de
HttpClient
. - Cree una instancia de
GitHubService
, pasando la instancia deHttpClient
a su constructor.
y se puede insertar y consumir directamente:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public TypedClientModel(GitHubService gitHubService) =>
_gitHubService = gitHubService;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
GitHubBranches = await _gitHubService.GetAspNetCoreDocsBranchesAsync();
}
catch (HttpRequestException)
{
// ...
}
}
}
La configuración de un cliente con tipo se puede especificar también durante su registro en Program.cs
, en lugar de en su constructor:
builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// ...
});
Clientes generados
IHttpClientFactory
se puede usar en combinación con bibliotecas de terceros, como Refit. Refit es una biblioteca de REST para .NET. Convierte las API de REST en interfaces en vivo. Llame a AddRefitClient
para generar una implementación dinámica de una interfaz, que usa HttpClient
para realizar las llamadas HTTP externas.
Una interfaz personalizada representa la API externa:
public interface IGitHubClient
{
[Get("/repos/dotnet/AspNetCore.Docs/branches")]
Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}
Llame a AddRefitClient
para generar la implementación dinámica y, luego, llame a ConfigureHttpClient
para configurar el elemento HttpClient
subyacente:
builder.Services.AddRefitClient<IGitHubClient>()
.ConfigureHttpClient(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});
Use DI para acceder a la implementación dinámica de IGitHubClient
:
public class RefitModel : PageModel
{
private readonly IGitHubClient _gitHubClient;
public RefitModel(IGitHubClient gitHubClient) =>
_gitHubClient = gitHubClient;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
GitHubBranches = await _gitHubClient.GetAspNetCoreDocsBranchesAsync();
}
catch (ApiException)
{
// ...
}
}
}
Realización de solicitudes POST, PUT y DELETE
En los ejemplos anteriores, todas las solicitudes HTTP usan el verbo HTTP de GET. HttpClient
también admite otros verbos HTTP; por ejemplo:
- POST
- PUT
- SUPRIMIR
- PATCH
Para obtener una lista completa de los verbos HTTP admitidos, vea HttpMethod.
En el ejemplo siguiente se muestra cómo hacer una solicitud POST HTTP:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json); // using static System.Net.Mime.MediaTypeNames;
using var httpResponseMessage =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponseMessage.EnsureSuccessStatusCode();
}
En el código anterior, el método CreateItemAsync
:
- Serializa el parámetro
TodoItem
en JSON medianteSystem.Text.Json
. - Crea una instancia de StringContent a fin de empaquetar el JSON serializado para enviarlo en el cuerpo de la solicitud de HTTP.
- Llama a PostAsync para enviar el contenido de JSON a la URL especificada. Se trata de una URL relativa que se agrega a HttpClient.BaseAddress.
- Llama a EnsureSuccessStatusCode para iniciar una excepción si el código de estado de la respuesta no indica que la operación se ha realizado correctamente.
HttpClient
también admite otros tipos de contenido. Por ejemplo, MultipartContent y StreamContent. Para obtener una lista completa del contenido admitido, consulta HttpContent.
En el ejemplo siguiente se muestra una solicitud PUT HTTP:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json);
using var httpResponseMessage =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponseMessage.EnsureSuccessStatusCode();
}
El código anterior es similar al ejemplo de POST. El método SaveItemAsync
llama a PutAsync en lugar de PostAsync
.
En el ejemplo siguiente se muestra una solicitud DELETE HTTP:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponseMessage =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponseMessage.EnsureSuccessStatusCode();
}
En el código anterior, el método DeleteItemAsync
llama a DeleteAsync. Debido a que las solicitudes DELETE HTTP normalmente no contienen ningún cuerpo, el método DeleteAsync
no proporciona ninguna sobrecarga que acepte una instancia de HttpContent
.
Para obtener más información sobre el uso de verbos HTTP diferentes con HttpClient
, consulta HttpClient.
Middleware de solicitud saliente
HttpClient
tiene el concepto de controladores de delegación, que se pueden vincular entre sí para las solicitudes HTTP salientes. IHttpClientFactory
:
- simplifica la definición de controladores que se aplicarán por cada cliente con nombre.
- Admite el registro y encadenamiento de varios controladores para crear una canalización de middleware de solicitud saliente. Cada uno de estos controladores es capaz de realizar la tarea antes y después de la solicitud de salida. Este patrón:
- Es similar a la canalización de middleware de entrada de ASP.NET Core.
- Proporciona un mecanismo para administrar los intereses transversales relacionados con las solicitudes HTTP, como:
- el almacenamiento en caché
- el control de errores
- la serialización
- el registro
Para crear un controlador de delegación:
- Derívalo de DelegatingHandler.
- Reemplaza SendAsync. Ejecuta el código antes de pasar la solicitud al siguiente controlador de la canalización:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"The API key header X-API-KEY is required.")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
El código anterior comprueba si el encabezado X-API-KEY
está en la solicitud. Si falta X-API-KEY
, se devuelve BadRequest.
Se puede agregar más de un controlador a la configuración de una instancia de HttpClient
con Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
builder.Services.AddTransient<ValidateHeaderHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<ValidateHeaderHandler>();
En el código anterior, ValidateHeaderHandler
se ha registrado con inserción de dependencias. Una vez registrado, se puede llamar a AddHttpMessageHandler, pasando el tipo del controlador.
Se pueden registrar varios controladores en el orden en que deben ejecutarse. Cada controlador contiene el siguiente controlador hasta que el último HttpClientHandler
ejecuta la solicitud:
builder.Services.AddTransient<SampleHandler1>();
builder.Services.AddTransient<SampleHandler2>();
builder.Services.AddHttpClient("MultipleHttpMessageHandlers")
.AddHttpMessageHandler<SampleHandler1>()
.AddHttpMessageHandler<SampleHandler2>();
En el código anterior, SampleHandler1
se ejecuta primero, antes que SampleHandler2
.
Uso de la inserción de dependencias en el middleware de solicitud de salida
Cuando IHttpClientFactory
crea un nuevo controlador de delegación, usa la inserción de dependencias para cumplir con los parámetros de constructor del controlador. IHttpClientFactory
crea un ámbito de inserción de dependencias independiente para cada controlador, lo que puede provocar un comportamiento sorprendente cuando un controlador consume un servicio con ámbito.
Por ejemplo, considera la siguiente interfaz y su implementación, que representa una tarea como una operación con un identificador, OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Como sugiere su nombre, IOperationScoped
se registra con la inserción de dependencias mediante una duración con ámbito:
builder.Services.AddScoped<IOperationScoped, OperationScoped>();
El siguiente controlador de delegación consume y usa IOperationScoped
para establecer el encabezado X-OPERATION-ID
para la solicitud de salida:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationScoped;
public OperationHandler(IOperationScoped operationScoped) =>
_operationScoped = operationScoped;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationScoped.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
En la descarga de HttpRequestsSample
, ve a /Operation
y actualiza la página. El valor del ámbito de la solicitud cambia para cada solicitud, pero el valor del ámbito del controlador solo cambia cada 5 segundos.
Los controladores pueden depender de servicios de cualquier ámbito. Los servicios de los que dependen los controladores se eliminan cuando se elimina el controlador.
Usa uno de los siguientes enfoques para compartir el estado por solicitud con controladores de mensajes:
- Pasa datos al controlador usando HttpRequestMessage.Options.
- Use IHttpContextAccessor para acceder a la solicitud actual.
- Cree un objeto de almacenamiento AsyncLocal<T> personalizado para pasar los datos.
Usar controladores basados en Polly
IHttpClientFactory
se integra con la biblioteca de terceros Polly. Polly es una biblioteca con capacidades de resistencia y control de errores transitorios para .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.
Se proporcionan métodos de extensión para hacer posible el uso de directivas de Polly con instancias de HttpClient
configuradas. Las extensiones de Polly permiten agregar controladores basados en Polly a los clientes. Polly requiere el paquete NuGet Microsoft.Extensions.Http.Polly.
Control de errores transitorios
Los errores se suelen producir cuando las llamadas HTTP externas son transitorias. AddTransientHttpErrorPolicy permite definir una directiva para controlar los errores transitorios. Las directivas configuradas con AddTransientHttpErrorPolicy
controlan las respuestas siguientes:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
proporciona acceso a un objeto PolicyBuilder
configurado para controlar los errores que representan un posible error transitorio:
builder.Services.AddHttpClient("PollyWaitAndRetry")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3, retryNumber => TimeSpan.FromMilliseconds(600)));
En el código anterior, se define una directiva WaitAndRetryAsync
. Las solicitudes erróneas se reintentan hasta tres veces con un retardo de 600 ms entre intentos.
Seleccionar directivas dinámicamente
Los métodos de extensión se proporcionan para agregar controladores basados en Polly, por ejemplo, AddPolicyHandler. La siguiente sobrecarga de AddPolicyHandler
inspecciona la solicitud para decidir qué directiva se debe aplicar:
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
builder.Services.AddHttpClient("PollyDynamic")
.AddPolicyHandler(httpRequestMessage =>
httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy : longTimeoutPolicy);
En el código anterior, si la solicitud GET saliente es del tipo HTTP, se aplica un tiempo de espera de 10 segundos. En cualquier otro método HTTP, se usa un tiempo de espera de 30 segundos.
Agregar varios controladores de Polly
Es común anidar las directivas de Polly:
builder.Services.AddHttpClient("PollyMultiple")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.RetryAsync(3))
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
En el ejemplo anterior:
- Se agregan dos controladores.
- El primer controlador usa AddTransientHttpErrorPolicy para agregar una directiva de reintentos. Las solicitudes con error se reintentan hasta tres veces.
- La segunda llamada a
AddTransientHttpErrorPolicy
agrega una directiva de interruptor. Las solicitudes externas adicionales se bloquean durante 30 segundos si se producen cinco intentos con error seguidos. Las directivas de interruptor tienen estado. Así, todas las llamadas realizadas a través de este cliente comparten el mismo estado de circuito.
Agregar directivas desde el Registro de Polly
Una forma de administrar las directivas usadas habitualmente consiste en definirlas una vez y registrarlas con PolicyRegistry
. Por ejemplo:
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var policyRegistry = builder.Services.AddPolicyRegistry();
policyRegistry.Add("Regular", timeoutPolicy);
policyRegistry.Add("Long", longTimeoutPolicy);
builder.Services.AddHttpClient("PollyRegistryRegular")
.AddPolicyHandlerFromRegistry("Regular");
builder.Services.AddHttpClient("PollyRegistryLong")
.AddPolicyHandlerFromRegistry("Long");
En el código anterior:
- Se agregan dos directivas,
Regular
yLong
, al registro de Polly. - AddPolicyHandlerFromRegistry configura clientes con nombre individuales para que usen estas directivas del registro de Polly.
Para más información sobre IHttpClientFactory
y las integraciones de Polly, vea la wiki de Polly.
HttpClient y administración de la duración
Cada vez que se llama a CreateClient
en IHttpClientFactory
, se devuelve una nueva instancia de HttpClient
. Se crea un objeto HttpMessageHandler por cada cliente con nombre. La fábrica administra la duración de las instancias de HttpMessageHandler
.
IHttpClientFactory
agrupa las instancias de HttpMessageHandler
creadas por Factory para reducir el consumo de recursos. Se puede reutilizar una instancia de HttpMessageHandler
del grupo al crear una instancia de HttpClient
si su duración aún no ha expirado.
Se recomienda agrupar controladores porque cada uno de ellos normalmente administra sus propias conexiones HTTP subyacentes. Crear más controladores de los necesarios puede provocar retrasos en la conexión. 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).
La duración de controlador predeterminada es dos minutos. El valor predeterminado se puede reemplazar de forma individual en cada cliente con nombre:
builder.Services.AddHttpClient("HandlerLifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Normalmente, las instancias de HttpClient
se pueden trata como objetos de .NET que no requieren eliminación. ya que se cancelan las solicitudes salientes y la instancia de HttpClient
determinada no se puede usar después de llamar a Dispose. IHttpClientFactory
realiza un seguimiento y elimina los recursos que usan las instancias de HttpClient
.
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
. Este patrón se convierte en innecesario tras la migración a IHttpClientFactory
.
Alternativas a IHttpClientFactory
El uso de IHttpClientFactory
en una aplicación habilitada para la inserción de dependencias evita lo siguiente:
- Problemas de agotamiento de recursos mediante la agrupación de instancias de
HttpMessageHandler
. - Problemas de DNS obsoletos al recorrer las instancias de
HttpMessageHandler
a intervalos regulares.
Existen formas alternativas de solucionar los problemas anteriores mediante una instancia de SocketsHttpHandler de larga duración.
- Cree una instancia de
SocketsHttpHandler
al iniciar la aplicación y úsela para la vida útil de la aplicación. - Configure PooledConnectionLifetime con un valor adecuado en función de los tiempos de actualización de DNS.
- Cree instancias de
HttpClient
mediantenew HttpClient(handler, disposeHandler: false)
según sea necesario.
Los enfoques anteriores solucionan los problemas de administración de recursos que IHttpClientFactory
resuelve de forma similar.
SocketsHttpHandler
comparte las conexiones entre las instancias deHttpClient
. Este uso compartido impide el agotamiento del socket.SocketsHttpHandler
recorre las conexiones segúnPooledConnectionLifetime
para evitar problemas de DNS obsoletos.
Registro
Los clientes que se han creado a través de IHttpClientFactory
registran mensajes de registro de todas las solicitudes. Habilite el nivel de información adecuado en la configuración del registro para ver los mensajes de registro predeterminados. El registro de más información, como el registro de encabezados de solicitud, solo se incluye en el nivel de seguimiento.
La categoría de registro usada en cada cliente incluye el nombre del cliente. Un cliente denominado MyNamedClient, por ejemplo, registra mensajes con una categoría de "System.Net.Http.HttpClient.MyNamedClient.LogicalHandler". Los mensajes con el sufijo LogicalHandler se producen fuera de la canalización de controlador de la solicitud. En la solicitud, los mensajes se registran antes de que cualquier otro controlador de la canalización haya procesado la solicitud. En la respuesta, los mensajes se registran después de que cualquier otro controlador de la canalización haya recibido la respuesta.
El registro también se produce dentro de la canalización de controlador de la solicitud. En el ejemplo MyNamedClient, esos mensajes se registran con la categoría de registro "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. En la respuesta, este registro incluye el estado de la respuesta antes de que vuelva a pasar por la canalización del controlador.
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. Esto puede incluir cambios en los encabezados de solicitud o en el código de estado de la respuesta.
La inclusión del nombre del cliente en la categoría de registro permite filtrar el registro para clientes con nombre específicos.
Configurar HttpMessageHandler
Puede que sea necesario controlar la configuración del elemento HttpMessageHandler
interno usado por un cliente.
Se devuelve un IHttpClientBuilder
cuando se agregan clientes con nombre o con tipo. Se puede usar el método de extensión ConfigurePrimaryHttpMessageHandler para definir un delegado. Este delegado servirá para crear y configurar el elemento principal HttpMessageHandler
que ese cliente usa:
builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
AllowAutoRedirect = true,
UseDefaultCredentials = true
});
Cookies
Las instancias de HttpMessageHandler
agrupadas generan objetos CookieContainer
que se comparten. El uso compartido de objetos CookieContainer
no previsto suele generar código incorrecto. En el caso de las aplicaciones que requieren cookies, tenga en cuenta lo siguiente:
- Deshabilitación del control automático de cookies
- Evitar
IHttpClientFactory
Llame a ConfigurePrimaryHttpMessageHandler para deshabilitar el control automático de cookies:
builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});
Uso de IHttpClientFactory en una aplicación de consola
En una aplicación de consola, agregue las siguientes referencias de paquete al proyecto:
En el ejemplo siguiente:
- IHttpClientFactory y
GitHubService
se registran en el contenedor de servicio del host genérico. GitHubService
se solicita desde DI, que, a su vez, solicita una instancia deIHttpClientFactory
.GitHubService
usaIHttpClientFactory
para crear una instancia deHttpClient
, que usa para recuperar ramas de GitHub de documentos.
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var host = new HostBuilder()
.ConfigureServices(services =>
{
services.AddHttpClient();
services.AddTransient<GitHubService>();
})
.Build();
try
{
var gitHubService = host.Services.GetRequiredService<GitHubService>();
var gitHubBranches = await gitHubService.GetAspNetCoreDocsBranchesAsync();
Console.WriteLine($"{gitHubBranches?.Count() ?? 0} GitHub Branches");
if (gitHubBranches is not null)
{
foreach (var gitHubBranch in gitHubBranches)
{
Console.WriteLine($"- {gitHubBranch.Name}");
}
}
}
catch (Exception ex)
{
host.Services.GetRequiredService<ILogger<Program>>()
.LogError(ex, "Unable to load branches from GitHub.");
}
public class GitHubService
{
private readonly IHttpClientFactory _httpClientFactory;
public GitHubService(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ "Accept", "application/vnd.github.v3+json" },
{ "User-Agent", "HttpRequestsConsoleSample" }
}
};
var httpClient = _httpClientFactory.CreateClient();
var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
httpResponseMessage.EnsureSuccessStatusCode();
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
public record GitHubBranch(
[property: JsonPropertyName("name")] string Name);
Middleware de propagación de encabezados
La propagación de encabezados es un middleware de ASP.NET Core que se usa para propagar encabezados HTTP de la solicitud entrante a las solicitudes HttpClient
salientes. Para utilizar la propagación de encabezados:
Instale el paquete Microsoft.AspNetCore.HeaderPropagation.
Configure
HttpClient
y la canalización de middleware enProgram.cs
:// Add services to the container. builder.Services.AddControllers(); builder.Services.AddHttpClient("PropagateHeaders") .AddHeaderPropagation(); builder.Services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.MapControllers();
Realice solicitudes salientes mediante la instancia de
HttpClient
configurada, que incluye los encabezados agregados.
Recursos adicionales
- Vea o descargue el código de ejemplo (cómo descargarlo)
- Uso de HttpClientFactory para implementar solicitudes HTTP resistentes
- Implementación de reintentos de llamada HTTP con retroceso exponencial con HttpClientFactory y las directivas de Polly
- Implementación del patrón de interruptor
- Procedimiento para serializar y deserializar JSON en .NET
Por Kirk Larkin, Steve Gordon, Glenn Condron y Ryan Nowak.
Se puede registrar y usar una interfaz IHttpClientFactory para crear y configurar instancias de HttpClient en una aplicación. IHttpClientFactory
ofrece las ventajas siguientes:
- Proporciona una ubicación central para denominar y configurar instancias de
HttpClient
lógicas. Por ejemplo, se podría registrar un cliente llamado github y configurarlo para acceder a GitHub. Se puede registrar un cliente predeterminado para el acceso general. - Codifica el concepto de middleware de salida a través de la delegación de controladores en
HttpClient
. Proporciona extensiones para el middleware basado en Polly a fin de aprovechar los controladores de delegación enHttpClient
. - Administra la agrupación y la duración de las instancias de
HttpClientMessageHandler
subyacentes. La administración automática evita los problemas comunes de DNS (Sistema de nombres de dominio) que se producen al administrar la duración deHttpClient
de forma manual. - 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.
Vea o descargue el código de ejemplo (cómo descargarlo).
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. 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.
Patrones de consumo
IHttpClientFactory
se puede usar de varias formas en una aplicación:
El mejor enfoque depende de los requisitos de la aplicación.
Uso básico
IHttpClientFactory
se puede registrar mediante una llamada a 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). En el código siguiente se usa IHttpClientFactory
para crear una instancia de HttpClient
:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
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. No tiene efecto alguno en la forma de usar HttpClient
. 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.
Clientes con nombre
Los clientes con nombre son una buena opción cuando:
- La aplicación requiere muchos usos distintos de
HttpClient
. - Muchas instancias
HttpClient
de tienen otra configuración.
La configuración de un objeto HttpClient
con nombre se puede realizar durante la fase de registro en 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:
- La dirección base.
https://api.github.com/
. - Dos encabezados necesarios para trabajar con la API de GitHub.
CreateClient
Cada vez que se llama a CreateClient:
- Se crea una instancia de
HttpClient
. - Se llama a la acción de configuración.
Para crear un cliente con nombre, pase su nombre a CreateClient
:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
PullRequests = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubPullRequest>>(responseStream);
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
En el código anterior, no es necesario especificar un nombre de host en la solicitud. El código puede pasar solo la ruta de acceso, ya que se usa la dirección base configurada del cliente.
Clientes con tipo
Clientes con tipo:
- Proporcionan las mismas funciones que los clientes con nombre sin la necesidad de usar cadenas como claves.
- Ofrecen ayuda relativa al compilador e IntelliSense al consumir clientes.
- Facilitan una sola ubicación para configurar un elemento
HttpClient
determinado e interactuar con él. Por ejemplo, es posible usar un solo cliente con tipo:- Para un único punto de conexión de back-end.
- Para encapsular toda la lógica relacionada con el punto de conexión.
- Funcionan con la inserción de dependencias y se pueden insertar en la aplicación cuando sea necesario.
Un cliente con tipo acepta un parámetro HttpClient
en su 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()
{
return await Client.GetFromJsonAsync<IEnumerable<GitHubIssue>>(
"/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
}
}
En el código anterior:
- La configuración se mueve al cliente con tipo.
- El objeto
HttpClient
se expone como una propiedad pública.
Se pueden crear métodos específicos de la API que exponen la funcionalidad de HttpClient
. Por ejemplo, el método GetAspNetDocsIssues
encapsula el código para recuperar incidencias abiertas.
En el código siguiente se llama a AddHttpClient en Startup.ConfigureServices
para registrar una clase de cliente con tipo:
services.AddHttpClient<GitHubService>();
El cliente con tipo se registra como transitorio con inserción con dependencias, En el código anterior, AddHttpClient
registra GitHubService
como servicio transitorio. Este registro usa un Factory Method para:
- Crea una instancia de
HttpClient
. - Cree una instancia de
GitHubService
, pasando la instancia deHttpClient
a su constructor.
y se puede insertar y consumir directamente:
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:
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. En lugar de exponerlo como una propiedad, defina un método que llame a la instancia de HttpClient
internamente:
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
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. El acceso a HttpClient
se realiza mediante el método GetRepos
público.
Clientes generados
IHttpClientFactory
se puede usar en combinación con bibliotecas de terceros, como Refit. Refit es una biblioteca de REST para .NET. Convierte las API de REST en interfaces en vivo. Se genera una implementación de la interfaz dinámicamente por medio de RestService
, usando HttpClient
para realizar las llamadas HTTP externas.
Se define una interfaz y una respuesta para representar la API externa y su correspondiente respuesta:
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:
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:
[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();
}
}
Realización de solicitudes POST, PUT y DELETE
En los ejemplos anteriores, todas las solicitudes HTTP usan el verbo HTTP de GET. HttpClient
también admite otros verbos HTTP; por ejemplo:
- POST
- PUT
- SUPRIMIR
- PATCH
Para obtener una lista completa de los verbos HTTP admitidos, vea HttpMethod.
En el ejemplo siguiente se muestra cómo hacer una solicitud POST HTTP:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
En el código anterior, el método CreateItemAsync
:
- Serializa el parámetro
TodoItem
en JSON medianteSystem.Text.Json
. Usa una instancia de JsonSerializerOptions para configurar el proceso de serialización. - Crea una instancia de StringContent a fin de empaquetar el JSON serializado para enviarlo en el cuerpo de la solicitud de HTTP.
- Llama a PostAsync para enviar el contenido de JSON a la URL especificada. Se trata de una URL relativa que se agrega a HttpClient.BaseAddress.
- Llama a EnsureSuccessStatusCode para iniciar una excepción si el código de estado de la respuesta no indica que la operación se ha procesado correctamente.
HttpClient
también admite otros tipos de contenido. Por ejemplo, MultipartContent y StreamContent. Para obtener una lista completa del contenido admitido, consulta HttpContent.
En el ejemplo siguiente se muestra una solicitud PUT HTTP:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
El código anterior es muy similar al ejemplo de POST. El método SaveItemAsync
llama a PutAsync en lugar de PostAsync
.
En el ejemplo siguiente se muestra una solicitud DELETE HTTP:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponse.EnsureSuccessStatusCode();
}
En el código anterior, el método DeleteItemAsync
llama a DeleteAsync. Debido a que las solicitudes DELETE HTTP normalmente no contienen ningún cuerpo, el método DeleteAsync
no proporciona ninguna sobrecarga que acepte una instancia de HttpContent
.
Para obtener más información sobre el uso de verbos HTTP diferentes con HttpClient
, consulta HttpClient.
Middleware de solicitud saliente
HttpClient
tiene el concepto de controladores de delegación, que se pueden vincular entre sí para las solicitudes HTTP salientes. IHttpClientFactory
:
- simplifica la definición de controladores que se aplicarán por cada cliente con nombre.
- Admite el registro y encadenamiento de varios controladores para crear una canalización de middleware de solicitud saliente. Cada uno de estos controladores es capaz de realizar la tarea antes y después de la solicitud de salida. Este patrón:
- Es similar a la canalización de middleware de entrada de ASP.NET Core.
- Proporciona un mecanismo para administrar los intereses transversales relacionados con las solicitudes HTTP, como:
- el almacenamiento en caché
- el control de errores
- la serialización
- el registro
Para crear un controlador de delegación:
- Derívalo de DelegatingHandler.
- Reemplaza SendAsync. Ejecuta el código antes de pasar la solicitud al siguiente controlador de la canalización:
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. Si falta X-API-KEY
, se devuelve BadRequest.
Se puede agregar más de un controlador a la configuración de una instancia de HttpClient
con 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. Una vez registrado, se puede llamar a AddHttpMessageHandler, pasando el tipo del controlador.
Se pueden registrar varios controladores en el orden en que deben ejecutarse. Cada controlador contiene el siguiente controlador hasta que el último HttpClientHandler
ejecuta la solicitud:
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>();
Uso de la inserción de dependencias en el middleware de solicitud de salida
Cuando IHttpClientFactory
crea un nuevo controlador de delegación, usa la inserción de dependencias para cumplir con los parámetros de constructor del controlador. IHttpClientFactory
crea un ámbito de inserción de dependencias independiente para cada controlador, lo que puede provocar un comportamiento sorprendente cuando un controlador consume un servicio con ámbito.
Por ejemplo, considera la siguiente interfaz y su implementación, que representa una tarea como una operación con un identificador, OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Como sugiere su nombre, IOperationScoped
se registra con la inserción de dependencias mediante una duración con ámbito:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoItems"));
services.AddHttpContextAccessor();
services.AddHttpClient<TodoClient>((sp, httpClient) =>
{
var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;
// For sample purposes, assume TodoClient is used in the context of an incoming request.
httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
httpRequest.Host, httpRequest.PathBase));
httpClient.Timeout = TimeSpan.FromSeconds(5);
});
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddTransient<OperationHandler>();
services.AddTransient<OperationResponseHandler>();
services.AddHttpClient("Operation")
.AddHttpMessageHandler<OperationHandler>()
.AddHttpMessageHandler<OperationResponseHandler>()
.SetHandlerLifetime(TimeSpan.FromSeconds(5));
services.AddControllers();
services.AddRazorPages();
}
El siguiente controlador de delegación consume y usa IOperationScoped
para establecer el encabezado X-OPERATION-ID
para la solicitud de salida:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationService;
public OperationHandler(IOperationScoped operationScoped)
{
_operationService = operationScoped;
}
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
En la descarga de HttpRequestsSample
, vaya a /Operation
y actualice la página. El valor del ámbito de la solicitud cambia para cada solicitud, pero el valor del ámbito del controlador solo cambia cada 5 segundos.
Los controladores pueden depender de servicios de cualquier ámbito. Los servicios de los que dependen los controladores se eliminan cuando se elimina el controlador.
Usa uno de los siguientes enfoques para compartir el estado por solicitud con controladores de mensajes:
- Pasa datos al controlador usando HttpRequestMessage.Options.
- Use IHttpContextAccessor para acceder a la solicitud actual.
- Cree un objeto de almacenamiento AsyncLocal<T> personalizado para pasar los datos.
Usar controladores basados en Polly
IHttpClientFactory
se integra con la biblioteca de terceros Polly. Polly es una biblioteca con capacidades de resistencia y control de errores transitorios para .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.
Se proporcionan métodos de extensión para hacer posible el uso de directivas de Polly con instancias de HttpClient
configuradas. Las extensiones de Polly permiten agregar controladores basados en Polly a los clientes. Polly requiere el paquete NuGet Microsoft.Extensions.Http.Polly.
Control de errores transitorios
Los errores se suelen producir cuando las llamadas HTTP externas son transitorias. AddTransientHttpErrorPolicy permite definir una directiva para controlar los errores transitorios. Las directivas configuradas con AddTransientHttpErrorPolicy
controlan las respuestas siguientes:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
proporciona acceso a un objeto PolicyBuilder
configurado para controlar los errores que representan un posible error transitorio:
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
. Las solicitudes erróneas se reintentan hasta tres veces con un retardo de 600 ms entre intentos.
Seleccionar directivas dinámicamente
Los métodos de extensión se proporcionan para agregar controladores basados en Polly, por ejemplo, AddPolicyHandler. La siguiente sobrecarga de AddPolicyHandler
inspecciona la solicitud para decidir qué directiva se debe aplicar:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
En el código anterior, si la solicitud GET saliente es del tipo HTTP, se aplica un tiempo de espera de 10 segundos. En cualquier otro método HTTP, se usa un tiempo de espera de 30 segundos.
Agregar varios controladores de Polly
Es común anidar las directivas de Polly:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
En el ejemplo anterior:
- Se agregan dos controladores.
- El primer controlador usa AddTransientHttpErrorPolicy para agregar una directiva de reintentos. Las solicitudes con error se reintentan hasta tres veces.
- La segunda llamada a
AddTransientHttpErrorPolicy
agrega una directiva de interruptor. Las solicitudes externas adicionales se bloquean durante 30 segundos si se producen cinco intentos con error seguidos. Las directivas de interruptor tienen estado. Así, todas las llamadas realizadas a través de este cliente comparten el mismo estado de circuito.
Agregar directivas desde el Registro de Polly
Una forma de administrar las directivas usadas habitualmente consiste en definirlas una vez y registrarlas con PolicyRegistry
.
En el código siguiente:
- Se agregan las directivas "regular" y "long".
- AddPolicyHandlerFromRegistry agrega las directivas "regular" y "long" del registro.
public void ConfigureServices(IServiceCollection services)
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regularTimeoutHandler")
.AddPolicyHandlerFromRegistry("regular");
services.AddHttpClient("longTimeoutHandler")
.AddPolicyHandlerFromRegistry("long");
// Remaining code deleted for brevity.
Para más información sobre IHttpClientFactory
y las integraciones de Polly, vea la wiki de Polly.
HttpClient y administración de la duración
Cada vez que se llama a CreateClient
en IHttpClientFactory
, se devuelve una nueva instancia de HttpClient
. Se crea un objeto HttpMessageHandler por cada cliente con nombre. La fábrica administra la duración de las instancias de HttpMessageHandler
.
IHttpClientFactory
agrupa las instancias de HttpMessageHandler
creadas por Factory para reducir el consumo de recursos. Se puede reutilizar una instancia de HttpMessageHandler
del grupo al crear una instancia de HttpClient
si su duración aún no ha expirado.
Se recomienda agrupar controladores porque cada uno de ellos normalmente administra sus propias conexiones HTTP subyacentes. Crear más controladores de los necesarios puede provocar retrasos en la conexión. 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).
La duración de controlador predeterminada es dos minutos. El valor predeterminado se puede reemplazar de forma individual en cada cliente con nombre:
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. ya que se cancelan las solicitudes salientes y la instancia de HttpClient
determinada no se puede usar después de llamar a Dispose. IHttpClientFactory
realiza un seguimiento y elimina los recursos que usan las instancias de HttpClient
.
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
. Este patrón se convierte en innecesario tras la migración a IHttpClientFactory
.
Alternativas a IHttpClientFactory
El uso de IHttpClientFactory
en una aplicación habilitada para la inserción de dependencias evita lo siguiente:
- Problemas de agotamiento de recursos mediante la agrupación de instancias de
HttpMessageHandler
. - Problemas de DNS obsoletos al recorrer las instancias de
HttpMessageHandler
a intervalos regulares.
Existen formas alternativas de solucionar los problemas anteriores mediante una instancia de SocketsHttpHandler de larga duración.
- Cree una instancia de
SocketsHttpHandler
al iniciar la aplicación y úsela para la vida útil de la aplicación. - Configure PooledConnectionLifetime con un valor adecuado en función de los tiempos de actualización de DNS.
- Cree instancias de
HttpClient
mediantenew HttpClient(handler, disposeHandler: false)
según sea necesario.
Los enfoques anteriores solucionan los problemas de administración de recursos que IHttpClientFactory
resuelve de forma similar.
SocketsHttpHandler
comparte las conexiones entre las instancias deHttpClient
. Este uso compartido impide el agotamiento del socket.SocketsHttpHandler
recorre las conexiones segúnPooledConnectionLifetime
para evitar problemas de DNS obsoletos.
Cookies
Las instancias de HttpMessageHandler
agrupadas generan objetos CookieContainer
que se comparten. El uso compartido de objetos CookieContainer
no previsto suele generar código incorrecto. En el caso de las aplicaciones que requieren cookies, tenga en cuenta lo siguiente:
- Deshabilitación del control automático de cookies
- Evitar
IHttpClientFactory
Llame a ConfigurePrimaryHttpMessageHandler para deshabilitar el control automático de cookies:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Registro
Los clientes que se han creado a través de IHttpClientFactory
registran mensajes de registro de todas las solicitudes. Habilite el nivel de información adecuado en la configuración del registro para ver los mensajes de registro predeterminados. El registro de más información, como el registro de encabezados de solicitud, solo se incluye en el nivel de seguimiento.
La categoría de registro usada en cada cliente incluye el nombre del cliente. Un cliente denominado MyNamedClient, por ejemplo, registra mensajes con una categoría de "System.Net.Http.HttpClient.MyNamedClient.LogicalHandler". Los mensajes con el sufijo LogicalHandler se producen fuera de la canalización de controlador de la solicitud. En la solicitud, los mensajes se registran antes de que cualquier otro controlador de la canalización haya procesado la solicitud. En la respuesta, los mensajes se registran después de que cualquier otro controlador de la canalización haya recibido la respuesta.
El registro también se produce dentro de la canalización de controlador de la solicitud. En el ejemplo MyNamedClient, esos mensajes se registran con la categoría de registro "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. En la respuesta, este registro incluye el estado de la respuesta antes de que vuelva a pasar por la canalización del controlador.
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. Esto puede incluir cambios en los encabezados de solicitud o en el código de estado de la respuesta.
La inclusión del nombre del cliente en la categoría de registro permite filtrar el registro para clientes con nombre específicos.
Configurar HttpMessageHandler
Puede que sea necesario controlar la configuración del elemento HttpMessageHandler
interno usado por un cliente.
Se devuelve un IHttpClientBuilder
cuando se agregan clientes con nombre o con tipo. Se puede usar el método de extensión ConfigurePrimaryHttpMessageHandler para definir un delegado. Este delegado servirá para crear y configurar el elemento principal HttpMessageHandler
que ese cliente usa:
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 consola
En una aplicación de consola, agregue las siguientes referencias de paquete al proyecto:
En el ejemplo siguiente:
- IHttpClientFactory está registrado en el contenedor de servicios del host genérico.
MyService
crea una instancia de generador de clientes a partir del servicio, que se usa para crear un elementoHttpClient
.HttpClient
se utiliza para recuperar una página web.Main
crea un ámbito para ejecutar el métodoGetPage
del servicio y escribe los primeros 500 caracteres del contenido de la página web en la consola.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware de propagación de encabezados
La propagación de encabezados es un middleware ASP.NET Core que se usa para propagar encabezados HTTP de la solicitud entrante a las solicitudes de cliente HTTP salientes. Para utilizar la propagación de encabezados:
Haga referencia al paquete Microsoft.AspNetCore.HeaderPropagation.
Configure el middleware y
HttpClient
enStartup
:public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
El cliente incluye los encabezados configurados en las solicitudes salientes:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Recursos adicionales
Por Kirk Larkin, Steve Gordon, Glenn Condron y Ryan Nowak.
Se puede registrar y usar una interfaz IHttpClientFactory para crear y configurar instancias de HttpClient en una aplicación. IHttpClientFactory
ofrece las ventajas siguientes:
- Proporciona una ubicación central para denominar y configurar instancias de
HttpClient
lógicas. Por ejemplo, se podría registrar un cliente llamado github y configurarlo para acceder a GitHub. Se puede registrar un cliente predeterminado para el acceso general. - Codifica el concepto de middleware de salida a través de la delegación de controladores en
HttpClient
. Proporciona extensiones para el middleware basado en Polly a fin de aprovechar los controladores de delegación enHttpClient
. - Administra la agrupación y la duración de las instancias de
HttpClientMessageHandler
subyacentes. La administración automática evita los problemas comunes de DNS (Sistema de nombres de dominio) que se producen al administrar la duración deHttpClient
de forma manual. - 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.
Vea o descargue el código de ejemplo (cómo descargarlo).
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. 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.
Patrones de consumo
IHttpClientFactory
se puede usar de varias formas en una aplicación:
El mejor enfoque depende de los requisitos de la aplicación.
Uso básico
IHttpClientFactory
se puede registrar mediante una llamada a 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). En el código siguiente se usa IHttpClientFactory
para crear una instancia de HttpClient
:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
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. No tiene efecto alguno en la forma de usar HttpClient
. 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.
Clientes con nombre
Los clientes con nombre son una buena opción cuando:
- La aplicación requiere muchos usos distintos de
HttpClient
. - Muchas instancias
HttpClient
de tienen otra configuración.
La configuración de un objeto HttpClient
con nombre se puede realizar durante la fase de registro en 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:
- La dirección base.
https://api.github.com/
. - Dos encabezados necesarios para trabajar con la API de GitHub.
CreateClient
Cada vez que se llama a CreateClient:
- Se crea una instancia de
HttpClient
. - Se llama a la acción de configuración.
Para crear un cliente con nombre, pase su nombre a CreateClient
:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
PullRequests = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubPullRequest>>(responseStream);
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
En el código anterior, no es necesario especificar un nombre de host en la solicitud. El código puede pasar solo la ruta de acceso, ya que se usa la dirección base configurada del cliente.
Clientes con tipo
Clientes con tipo:
- Proporcionan las mismas funciones que los clientes con nombre sin la necesidad de usar cadenas como claves.
- Ofrecen ayuda relativa al compilador e IntelliSense al consumir clientes.
- Facilitan una sola ubicación para configurar un elemento
HttpClient
determinado e interactuar con él. Por ejemplo, es posible usar un solo cliente con tipo:- Para un único punto de conexión de back-end.
- Para encapsular toda la lógica relacionada con el punto de conexión.
- Funcionan con la inserción de dependencias y se pueden insertar en la aplicación cuando sea necesario.
Un cliente con tipo acepta un parámetro HttpClient
en su 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/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubIssue>>(responseStream);
}
}
Si quiere que los comentarios de código se traduzcan en más idiomas además del inglés, háganoslo saber en este problema de debate de GitHub.
En el código anterior:
- La configuración se mueve al cliente con tipo.
- El objeto
HttpClient
se expone como una propiedad pública.
Se pueden crear métodos específicos de la API que exponen la funcionalidad de HttpClient
. Por ejemplo, el método GetAspNetDocsIssues
encapsula el código para recuperar incidencias abiertas.
En el código siguiente se llama a AddHttpClient en Startup.ConfigureServices
para registrar una clase de cliente con tipo:
services.AddHttpClient<GitHubService>();
El cliente con tipo se registra como transitorio con inserción con dependencias, En el código anterior, AddHttpClient
registra GitHubService
como servicio transitorio. Este registro usa un Factory Method para:
- Crea una instancia de
HttpClient
. - Cree una instancia de
GitHubService
, pasando la instancia deHttpClient
a su constructor.
y se puede insertar y consumir directamente:
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:
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. En lugar de exponerlo como una propiedad, defina un método que llame a la instancia de HttpClient
internamente:
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
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. El acceso a HttpClient
se realiza mediante el método GetRepos
público.
Clientes generados
IHttpClientFactory
se puede usar en combinación con bibliotecas de terceros, como Refit. Refit es una biblioteca de REST para .NET. Convierte las API de REST en interfaces en vivo. Se genera una implementación de la interfaz dinámicamente por medio de RestService
, usando HttpClient
para realizar las llamadas HTTP externas.
Se define una interfaz y una respuesta para representar la API externa y su correspondiente respuesta:
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:
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:
[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();
}
}
Realización de solicitudes POST, PUT y DELETE
En los ejemplos anteriores, todas las solicitudes HTTP usan el verbo HTTP de GET. HttpClient
también admite otros verbos HTTP; por ejemplo:
- POST
- PUT
- SUPRIMIR
- PATCH
Para obtener una lista completa de los verbos HTTP admitidos, vea HttpMethod.
En el ejemplo siguiente se muestra cómo hacer una solicitud POST HTTP:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
En el código anterior, el método CreateItemAsync
:
- Serializa el parámetro
TodoItem
en JSON medianteSystem.Text.Json
. Usa una instancia de JsonSerializerOptions para configurar el proceso de serialización. - Crea una instancia de StringContent a fin de empaquetar el JSON serializado para enviarlo en el cuerpo de la solicitud de HTTP.
- Llama a PostAsync para enviar el contenido de JSON a la URL especificada. Se trata de una URL relativa que se agrega a HttpClient.BaseAddress.
- Llama a EnsureSuccessStatusCode para iniciar una excepción si el código de estado de la respuesta no indica que la operación se ha procesado correctamente.
HttpClient
también admite otros tipos de contenido. Por ejemplo, MultipartContent y StreamContent. Para obtener una lista completa del contenido admitido, consulta HttpContent.
En el ejemplo siguiente se muestra una solicitud PUT HTTP:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
El código anterior es muy similar al ejemplo de POST. El método SaveItemAsync
llama a PutAsync en lugar de PostAsync
.
En el ejemplo siguiente se muestra una solicitud DELETE HTTP:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponse.EnsureSuccessStatusCode();
}
En el código anterior, el método DeleteItemAsync
llama a DeleteAsync. Debido a que las solicitudes DELETE HTTP normalmente no contienen ningún cuerpo, el método DeleteAsync
no proporciona ninguna sobrecarga que acepte una instancia de HttpContent
.
Para obtener más información sobre el uso de verbos HTTP diferentes con HttpClient
, consulta HttpClient.
Middleware de solicitud saliente
HttpClient
tiene el concepto de controladores de delegación, que se pueden vincular entre sí para las solicitudes HTTP salientes. IHttpClientFactory
:
- simplifica la definición de controladores que se aplicarán por cada cliente con nombre.
- Admite el registro y encadenamiento de varios controladores para crear una canalización de middleware de solicitud saliente. Cada uno de estos controladores es capaz de realizar la tarea antes y después de la solicitud de salida. Este patrón:
- Es similar a la canalización de middleware de entrada de ASP.NET Core.
- Proporciona un mecanismo para administrar los intereses transversales relacionados con las solicitudes HTTP, como:
- el almacenamiento en caché
- el control de errores
- la serialización
- el registro
Para crear un controlador de delegación:
- Derívalo de DelegatingHandler.
- Reemplaza SendAsync. Ejecuta el código antes de pasar la solicitud al siguiente controlador de la canalización:
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. Si falta X-API-KEY
, se devuelve BadRequest.
Se puede agregar más de un controlador a la configuración de una instancia de HttpClient
con 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. Una vez registrado, se puede llamar a AddHttpMessageHandler, pasando el tipo del controlador.
Se pueden registrar varios controladores en el orden en que deben ejecutarse. Cada controlador contiene el siguiente controlador hasta que el último HttpClientHandler
ejecuta la solicitud:
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>();
Uso de la inserción de dependencias en el middleware de solicitud de salida
Cuando IHttpClientFactory
crea un nuevo controlador de delegación, usa la inserción de dependencias para cumplir con los parámetros de constructor del controlador. IHttpClientFactory
crea un ámbito de inserción de dependencias independiente para cada controlador, lo que puede provocar un comportamiento sorprendente cuando un controlador consume un servicio con ámbito.
Por ejemplo, considera la siguiente interfaz y su implementación, que representa una tarea como una operación con un identificador, OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Como sugiere su nombre, IOperationScoped
se registra con la inserción de dependencias mediante una duración con ámbito:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoItems"));
services.AddHttpContextAccessor();
services.AddHttpClient<TodoClient>((sp, httpClient) =>
{
var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;
// For sample purposes, assume TodoClient is used in the context of an incoming request.
httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
httpRequest.Host, httpRequest.PathBase));
httpClient.Timeout = TimeSpan.FromSeconds(5);
});
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddTransient<OperationHandler>();
services.AddTransient<OperationResponseHandler>();
services.AddHttpClient("Operation")
.AddHttpMessageHandler<OperationHandler>()
.AddHttpMessageHandler<OperationResponseHandler>()
.SetHandlerLifetime(TimeSpan.FromSeconds(5));
services.AddControllers();
services.AddRazorPages();
}
El siguiente controlador de delegación consume y usa IOperationScoped
para establecer el encabezado X-OPERATION-ID
para la solicitud de salida:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationService;
public OperationHandler(IOperationScoped operationScoped)
{
_operationService = operationScoped;
}
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
En la descarga de HttpRequestsSample
, vaya a /Operation
y actualice la página. El valor del ámbito de la solicitud cambia para cada solicitud, pero el valor del ámbito del controlador solo cambia cada 5 segundos.
Los controladores pueden depender de servicios de cualquier ámbito. Los servicios de los que dependen los controladores se eliminan cuando se elimina el controlador.
Usa uno de los siguientes enfoques para compartir el estado por solicitud con controladores de mensajes:
- Pasa datos al controlador usando HttpRequestMessage.Properties.
- Use IHttpContextAccessor para acceder a la solicitud actual.
- Cree un objeto de almacenamiento AsyncLocal<T> personalizado para pasar los datos.
Usar controladores basados en Polly
IHttpClientFactory
se integra con la biblioteca de terceros Polly. Polly es una biblioteca con capacidades de resistencia y control de errores transitorios para .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.
Se proporcionan métodos de extensión para hacer posible el uso de directivas de Polly con instancias de HttpClient
configuradas. Las extensiones de Polly permiten agregar controladores basados en Polly a los clientes. Polly requiere el paquete NuGet Microsoft.Extensions.Http.Polly.
Control de errores transitorios
Los errores se suelen producir cuando las llamadas HTTP externas son transitorias. AddTransientHttpErrorPolicy permite definir una directiva para controlar los errores transitorios. Las directivas configuradas con AddTransientHttpErrorPolicy
controlan las respuestas siguientes:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
proporciona acceso a un objeto PolicyBuilder
configurado para controlar los errores que representan un posible error transitorio:
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
. Las solicitudes erróneas se reintentan hasta tres veces con un retardo de 600 ms entre intentos.
Seleccionar directivas dinámicamente
Los métodos de extensión se proporcionan para agregar controladores basados en Polly, por ejemplo, AddPolicyHandler. La siguiente sobrecarga de AddPolicyHandler
inspecciona la solicitud para decidir qué directiva se debe aplicar:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
En el código anterior, si la solicitud GET saliente es del tipo HTTP, se aplica un tiempo de espera de 10 segundos. En cualquier otro método HTTP, se usa un tiempo de espera de 30 segundos.
Agregar varios controladores de Polly
Es común anidar las directivas de Polly:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
En el ejemplo anterior:
- Se agregan dos controladores.
- El primer controlador usa AddTransientHttpErrorPolicy para agregar una directiva de reintentos. Las solicitudes con error se reintentan hasta tres veces.
- La segunda llamada a
AddTransientHttpErrorPolicy
agrega una directiva de interruptor. Las solicitudes externas adicionales se bloquean durante 30 segundos si se producen cinco intentos con error seguidos. Las directivas de interruptor tienen estado. Así, todas las llamadas realizadas a través de este cliente comparten el mismo estado de circuito.
Agregar directivas desde el Registro de Polly
Una forma de administrar las directivas usadas habitualmente consiste en definirlas una vez y registrarlas con PolicyRegistry
.
En el código siguiente:
- Se agregan las directivas "regular" y "long".
- AddPolicyHandlerFromRegistry agrega las directivas "regular" y "long" del registro.
public void ConfigureServices(IServiceCollection services)
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regularTimeoutHandler")
.AddPolicyHandlerFromRegistry("regular");
services.AddHttpClient("longTimeoutHandler")
.AddPolicyHandlerFromRegistry("long");
// Remaining code deleted for brevity.
Para más información sobre IHttpClientFactory
y las integraciones de Polly, vea la wiki de Polly.
HttpClient y administración de la duración
Cada vez que se llama a CreateClient
en IHttpClientFactory
, se devuelve una nueva instancia de HttpClient
. Se crea un objeto HttpMessageHandler por cada cliente con nombre. La fábrica administra la duración de las instancias de HttpMessageHandler
.
IHttpClientFactory
agrupa las instancias de HttpMessageHandler
creadas por Factory para reducir el consumo de recursos. Se puede reutilizar una instancia de HttpMessageHandler
del grupo al crear una instancia de HttpClient
si su duración aún no ha expirado.
Se recomienda agrupar controladores porque cada uno de ellos normalmente administra sus propias conexiones HTTP subyacentes. Crear más controladores de los necesarios puede provocar retrasos en la conexión. 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).
La duración de controlador predeterminada es dos minutos. El valor predeterminado se puede reemplazar de forma individual en cada cliente con nombre:
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. ya que se cancelan las solicitudes salientes y la instancia de HttpClient
determinada no se puede usar después de llamar a Dispose. IHttpClientFactory
realiza un seguimiento y elimina los recursos que usan las instancias de HttpClient
.
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
. Este patrón se convierte en innecesario tras la migración a IHttpClientFactory
.
Alternativas a IHttpClientFactory
El uso de IHttpClientFactory
en una aplicación habilitada para la inserción de dependencias evita lo siguiente:
- Problemas de agotamiento de recursos mediante la agrupación de instancias de
HttpMessageHandler
. - Problemas de DNS obsoletos al recorrer las instancias de
HttpMessageHandler
a intervalos regulares.
Existen formas alternativas de solucionar los problemas anteriores mediante una instancia de SocketsHttpHandler de larga duración.
- Cree una instancia de
SocketsHttpHandler
al iniciar la aplicación y úsela para la vida útil de la aplicación. - Configure PooledConnectionLifetime con un valor adecuado en función de los tiempos de actualización de DNS.
- Cree instancias de
HttpClient
mediantenew HttpClient(handler, disposeHandler: false)
según sea necesario.
Los enfoques anteriores solucionan los problemas de administración de recursos que IHttpClientFactory
resuelve de forma similar.
SocketsHttpHandler
comparte las conexiones entre las instancias deHttpClient
. Este uso compartido impide el agotamiento del socket.SocketsHttpHandler
recorre las conexiones segúnPooledConnectionLifetime
para evitar problemas de DNS obsoletos.
Cookies
Las instancias de HttpMessageHandler
agrupadas generan objetos CookieContainer
que se comparten. El uso compartido de objetos CookieContainer
no previsto suele generar código incorrecto. En el caso de las aplicaciones que requieren cookies, tenga en cuenta lo siguiente:
- Deshabilitación del control automático de cookies
- Evitar
IHttpClientFactory
Llame a ConfigurePrimaryHttpMessageHandler para deshabilitar el control automático de cookies:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Registro
Los clientes que se han creado a través de IHttpClientFactory
registran mensajes de registro de todas las solicitudes. Habilite el nivel de información adecuado en la configuración del registro para ver los mensajes de registro predeterminados. El registro de más información, como el registro de encabezados de solicitud, solo se incluye en el nivel de seguimiento.
La categoría de registro usada en cada cliente incluye el nombre del cliente. Un cliente denominado MyNamedClient, por ejemplo, registra mensajes con una categoría de "System.Net.Http.HttpClient.MyNamedClient.LogicalHandler". Los mensajes con el sufijo LogicalHandler se producen fuera de la canalización de controlador de la solicitud. En la solicitud, los mensajes se registran antes de que cualquier otro controlador de la canalización haya procesado la solicitud. En la respuesta, los mensajes se registran después de que cualquier otro controlador de la canalización haya recibido la respuesta.
El registro también se produce dentro de la canalización de controlador de la solicitud. En el ejemplo MyNamedClient, esos mensajes se registran con la categoría de registro "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. En la respuesta, este registro incluye el estado de la respuesta antes de que vuelva a pasar por la canalización del controlador.
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. Esto puede incluir cambios en los encabezados de solicitud o en el código de estado de la respuesta.
La inclusión del nombre del cliente en la categoría de registro permite filtrar el registro para clientes con nombre específicos.
Configurar HttpMessageHandler
Puede que sea necesario controlar la configuración del elemento HttpMessageHandler
interno usado por un cliente.
Se devuelve un IHttpClientBuilder
cuando se agregan clientes con nombre o con tipo. Se puede usar el método de extensión ConfigurePrimaryHttpMessageHandler para definir un delegado. Este delegado servirá para crear y configurar el elemento principal HttpMessageHandler
que ese cliente usa:
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 consola
En una aplicación de consola, agregue las siguientes referencias de paquete al proyecto:
En el ejemplo siguiente:
- IHttpClientFactory está registrado en el contenedor de servicios del host genérico.
MyService
crea una instancia de generador de clientes a partir del servicio, que se usa para crear un elementoHttpClient
.HttpClient
se utiliza para recuperar una página web.Main
crea un ámbito para ejecutar el métodoGetPage
del servicio y escribe los primeros 500 caracteres del contenido de la página web en la consola.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware de propagación de encabezados
La propagación de encabezados es un middleware ASP.NET Core que se usa para propagar encabezados HTTP de la solicitud entrante a las solicitudes de cliente HTTP salientes. Para utilizar la propagación de encabezados:
Haga referencia al paquete Microsoft.AspNetCore.HeaderPropagation.
Configure el middleware y
HttpClient
enStartup
:public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
El cliente incluye los encabezados configurados en las solicitudes salientes:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Recursos adicionales
Por Glenn Condron, Ryan Nowak y Steve Gordon
Se puede registrar y usar una interfaz IHttpClientFactory para crear y configurar instancias de HttpClient en una aplicación. Esto reporta las siguientes ventajas:
- Proporciona una ubicación central para denominar y configurar instancias de
HttpClient
lógicas. Así, por ejemplo, se puede registrar y configurar un cliente github para tener acceso a GitHub. y, de igual modo, registrar otro cliente predeterminado para otros fines. - 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. - 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 deHttpClient
manualmente. - 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.
Vea o descargue el código de ejemplo (cómo descargarlo)
Requisitos previos
Los proyectos para .NET Framework requieren instalar el paquete NuGet Microsoft.Extensions.Http. Los proyectos para .NET Core y que hagan referencia al metapaquete Microsoft.AspNetCore.App ya incluyen el paquete Microsoft.Extensions.Http
.
Patrones de consumo
IHttpClientFactory
se puede usar de varias formas en una aplicación:
Ninguno de ellos es rigurosamente superior a otro, sino que el mejor método dependerá de las restricciones particulares de la aplicación.
Uso básico
Se puede registrar un IHttpClientFactory
llamando al método de extensión AddHttpClient
en IServiceCollection
, dentro del método Startup.ConfigureServices
.
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). IHttpClientFactory
se puede usar para crear una instancia de HttpClient
:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
Branches = await response.Content
.ReadAsAsync<IEnumerable<GitHubBranch>>();
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Cuando IHttpClientFactory
se usa de este modo, constituye una excelente manera de refactorizar una aplicación existente. No tiene efecto alguno en la forma en que HttpClient
se usa. En aquellos sitios en los que ya se hayan creado instancias de HttpClient
, reemplace esas apariciones por una llamada a CreateClient.
Clientes con nombre
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. La configuración de un HttpClient
con nombre se puede realizar durante la fase de registro en 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. Este cliente tiene aplicadas algunas configuraciones predeterminadas, a saber, la dirección base y dos encabezados necesarios para trabajar con la API de GitHub.
Cada vez que se llama a CreateClient
, se crea otra instancia de HttpClient
y se llama a la acción de configuración.
Para consumir un cliente con nombre, se puede pasar un parámetro de cadena a CreateClient
. Especifique el nombre del cliente que se va a crear:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
PullRequests = await response.Content
.ReadAsAsync<IEnumerable<GitHubPullRequest>>();
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
En el código anterior, no es necesario especificar un nombre de host en la solicitud. Basta con pasar solo la ruta de acceso, ya que se usa la dirección base configurada del cliente.
Clientes con tipo
Clientes con tipo:
- Proporcionan las mismas funciones que los clientes con nombre sin la necesidad de usar cadenas como claves.
- Ofrecen ayuda relativa al compilador e IntelliSense al consumir clientes.
- Facilitan una sola ubicación para configurar un elemento
HttpClient
determinado e interactuar con él. 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. - Funcionan con la inserción de dependencias y se pueden insertar en la aplicación cuando sea necesario.
Un cliente con tipo acepta un parámetro HttpClient
en su 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/dotnet/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. El objeto HttpClient
se expone como una propiedad pública. Se pueden definir métodos específicos de API que exponen la funcionalidad HttpClient
. El método GetAspNetDocsIssues
encapsula el código necesario para consultar y analizar los últimos problemas abiertos de un repositorio de GitHub.
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:
services.AddHttpClient<GitHubService>();
El cliente con tipo se registra como transitorio con inserción con dependencias, y se puede insertar y consumir directamente:
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:
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. En lugar de exponerlo como una propiedad, se pueden proporcionar métodos públicos que llamen a la instancia de HttpClient
internamente.
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadAsAsync<IEnumerable<string>>();
return result;
}
}
En el código anterior, el HttpClient
se almacena como un campo privado. Todo el acceso para realizar llamadas externas pasa por el método GetRepos
.
Clientes generados
IHttpClientFactory
se puede usar en combinación con otras bibliotecas de terceros, como Refit. Refit es una biblioteca de REST para .NET. Convierte las API de REST en interfaces en vivo. Se genera una implementación de la interfaz dinámicamente por medio de RestService
, usando HttpClient
para realizar las llamadas HTTP externas.
Se define una interfaz y una respuesta para representar la API externa y su correspondiente respuesta:
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:
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:
[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 saliente
HttpClient
ya posee el concepto de controladores de delegación, que se pueden vincular entre sí para las solicitudes HTTP salientes. IHttpClientFactory
permite definir fácilmente los controladores que se usarán en cada cliente con nombre. Admite el registro y encadenamiento de varios controladores para crear una canalización de middleware de solicitud saliente. Cada uno de estos controladores es capaz de realizar la tarea antes y después de la solicitud de salida. Este patrón es similar a la canalización de middleware de entrada de 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.
Para crear un controlador, defina una clase que se derive de DelegatingHandler. Invalide el método SendAsync
para ejecutar el código antes de pasar la solicitud al siguiente controlador de la canalización:
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. Comprueba si se ha incluido un encabezado X-API-KEY
en la solicitud. Si no está presente, puede evitar la llamada HTTP y devolver una respuesta adecuada.
Durante el registro, se pueden agregar uno o varios controladores a la configuración de una instancia de HttpClient
. Esta tarea se realiza a través de métodos de extensión en 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. El controlador debe estar registrado en la inserción de dependencias como servicio transitorio, nunca como servicio con ámbito. Si el controlador se registra como servicio con ámbito y se pueden eliminar los servicios de los que depende el controlador:
- Los servicios del controlador se pueden eliminar antes de que el controlador deje de estar en el ámbito.
- Los servicios del controlador que se eliminen provocarán un error en él.
Una vez registrado, se puede llamar a AddHttpMessageHandler, con lo que se pasa el tipo de controlador.
Se pueden registrar varios controladores en el orden en que deben ejecutarse. Cada controlador contiene el siguiente controlador hasta que el último HttpClientHandler
ejecuta la solicitud:
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:
- Pasa datos al controlador usando
HttpRequestMessage.Properties
. - Use
IHttpContextAccessor
para acceder a la solicitud actual. - Cree un objeto de almacenamiento
AsyncLocal
personalizado para pasar los datos.
Usar controladores basados en Polly
IHttpClientFactory
se integra con una biblioteca de terceros muy conocida denominada Polly. Polly es una biblioteca con capacidades de resistencia y control de errores transitorios para .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.
Se proporcionan métodos de extensión para hacer posible el uso de directivas de Polly con instancias de HttpClient
configuradas. Las extensiones de Polly:
- permiten agregar controladores basados en Polly a los clientes;
- se pueden usar tras instalar el paquete NuGet Microsoft.Extensions.Http.Polly, aunque este no está incluido en la plataforma compartida ASP.NET Core.
Control de errores transitorios
Los errores más comunes se producen cuando las llamadas HTTP externas son transitorias. 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. Las directivas que se configuran con este método de extensión controlan HttpRequestException
, las respuestas HTTP 5xx y las respuestas HTTP 408.
La extensión AddTransientHttpErrorPolicy
se puede usar en Startup.ConfigureServices
. La extensión da acceso a un objeto PolicyBuilder
, configurado para controlar los errores que pueden constituir un posible error transitorio:
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
En el código anterior, se define una directiva WaitAndRetryAsync
. Las solicitudes erróneas se reintentan hasta tres veces con un retardo de 600 ms entre intentos.
Seleccionar directivas dinámicamente
Existen más métodos de extensión que pueden servir para agregar controladores basados en Polly. Una de esas extensiones es AddPolicyHandler
, que tiene varias sobrecargas. Una de esas sobrecargas permite inspeccionar la solicitud al dilucidar qué directiva aplicar:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
En el código anterior, si la solicitud GET saliente es del tipo HTTP, se aplica un tiempo de espera de 10 segundos. En cualquier otro método HTTP, se usa un tiempo de espera de 30 segundos.
Agregar varios controladores de Polly
Es habitual anidar directivas de Polly para proporcionar una funcionalidad mejorada:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
En el ejemplo anterior, se agregan dos controladores. En el primer ejemplo se usa la extensión AddTransientHttpErrorPolicy
para agregar una directiva de reintento. Las solicitudes con error se reintentan hasta tres veces. La segunda llamada a AddTransientHttpErrorPolicy
agrega una directiva de interruptor. Las solicitudes externas subsiguientes se bloquean durante 30 segundos si se producen cinco intentos infructuosos seguidos. Las directivas de interruptor tienen estado. Así, todas las llamadas realizadas a través de este cliente comparten el mismo estado de circuito.
Agregar directivas desde el Registro de Polly
Una forma de administrar las directivas usadas habitualmente consiste en definirlas una vez y registrarlas con PolicyRegistry
. Se proporciona un método de extensión que permite agregar un controlador por medio de una directiva del Registro:
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
. Para usar una directiva del Registro, se usa el método AddPolicyHandlerFromRegistry
pasando el nombre de la directiva que se va a aplicar.
Encontrará más información sobre IHttpClientFactory
y las integraciones de Polly en la wiki de Polly.
HttpClient y administración de la duración
Cada vez que se llama a CreateClient
en IHttpClientFactory
, se devuelve una nueva instancia de HttpClient
. Hay un controlador HttpMessageHandler por cliente con nombre. La fábrica administra la duración de las instancias de HttpMessageHandler
.
IHttpClientFactory
agrupa las instancias de HttpMessageHandler
creadas por Factory para reducir el consumo de recursos. Se puede reutilizar una instancia de HttpMessageHandler
del grupo al crear una instancia de HttpClient
si su duración aún no ha expirado.
Se recomienda agrupar controladores porque cada uno de ellos normalmente administra sus propias conexiones HTTP subyacentes. Crear más controladores de los necesarios puede provocar retrasos en la conexión. 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.
La duración de controlador predeterminada es dos minutos. El valor predeterminado se puede reemplazar individualmente en cada cliente con nombre. Para ello, llame a SetHandlerLifetime en el IHttpClientBuilder
que se devuelve cuando se crea el cliente:
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
No hace falta eliminar el cliente, ya que se cancelan las solicitudes salientes y la instancia de HttpClient
determinada no se puede usar después de llamar a Dispose. IHttpClientFactory
realiza un seguimiento y elimina los recursos que usan las instancias de HttpClient
. Normalmente, las instancias de HttpClient
pueden tratarse como objetos de .NET que no requieren eliminación.
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
. Este patrón se convierte en innecesario tras la migración a IHttpClientFactory
.
Alternativas a IHttpClientFactory
El uso de IHttpClientFactory
en una aplicación habilitada para la inserción de dependencias evita lo siguiente:
- Problemas de agotamiento de recursos mediante la agrupación de instancias de
HttpMessageHandler
. - Problemas de DNS obsoletos al recorrer las instancias de
HttpMessageHandler
a intervalos regulares.
Existen formas alternativas de solucionar los problemas anteriores mediante una instancia de SocketsHttpHandler de larga duración.
- Cree una instancia de
SocketsHttpHandler
al iniciar la aplicación y úsela para la vida útil de la aplicación. - Configure PooledConnectionLifetime con un valor adecuado en función de los tiempos de actualización de DNS.
- Cree instancias de
HttpClient
mediantenew HttpClient(handler, disposeHandler: false)
según sea necesario.
Los enfoques anteriores solucionan los problemas de administración de recursos que IHttpClientFactory
resuelve de forma similar.
SocketsHttpHandler
comparte las conexiones entre las instancias deHttpClient
. Este uso compartido impide el agotamiento del socket.SocketsHttpHandler
recorre las conexiones segúnPooledConnectionLifetime
para evitar problemas de DNS obsoletos.
Cookies
Las instancias de HttpMessageHandler
agrupadas generan objetos CookieContainer
que se comparten. El uso compartido de objetos CookieContainer
no previsto suele generar código incorrecto. En el caso de las aplicaciones que requieren cookies, tenga en cuenta lo siguiente:
- Deshabilitación del control automático de cookies
- Evitar
IHttpClientFactory
Llame a ConfigurePrimaryHttpMessageHandler para deshabilitar el control automático de cookies:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Registro
Los clientes que se han creado a través de IHttpClientFactory
registran mensajes de registro de todas las solicitudes. Habilite el nivel de información adecuado en la configuración del registro para ver los mensajes de registro predeterminados. El registro de más información, como el registro de encabezados de solicitud, solo se incluye en el nivel de seguimiento.
La categoría de registro usada en cada cliente incluye el nombre del cliente. Así, por ejemplo, un cliente llamado MyNamedClient registra mensajes con una categoría System.Net.Http.HttpClient.MyNamedClient.LogicalHandler
. Los mensajes con el sufijo LogicalHandler se producen fuera de la canalización de controlador de la solicitud. En la solicitud, los mensajes se registran antes de que cualquier otro controlador de la canalización haya procesado la solicitud. En la respuesta, los mensajes se registran después de que cualquier otro controlador de la canalización haya recibido la respuesta.
El registro también se produce dentro de la canalización de controlador de la solicitud. En nuestro caso de ejemplo de MyNamedClient, esos mensajes se registran en la categoría de registro 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. En la respuesta, este registro incluye el estado de la respuesta antes de que vuelva a pasar por la canalización del controlador.
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. Esto puede englobar cambios, por ejemplo, en los encabezados de solicitud o en el código de estado de la respuesta.
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.
Configurar HttpMessageHandler
Puede que sea necesario controlar la configuración del elemento HttpMessageHandler
interno usado por un cliente.
Se devuelve un IHttpClientBuilder
cuando se agregan clientes con nombre o con tipo. Se puede usar el método de extensión ConfigurePrimaryHttpMessageHandler para definir un delegado. Este delegado servirá para crear y configurar el elemento principal HttpMessageHandler
que ese cliente usa:
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
Uso de IHttpClientFactory en una aplicación de consola
En una aplicación de consola, agregue las siguientes referencias de paquete al proyecto:
En el ejemplo siguiente:
- IHttpClientFactory está registrado en el contenedor de servicios del host genérico.
MyService
crea una instancia de generador de clientes a partir del servicio, que se usa para crear un elementoHttpClient
.HttpClient
se utiliza para recuperar una página web.- El método
GetPage
del servicio se ejecuta para escribir los primeros 500 caracteres del contenido de la página web en la consola. Para más información sobre la llamada a servicios desdeProgram.Main
, consulte Inserción de dependencias en ASP.NET Core.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware de propagación de encabezados
La propagación de encabezado es un middleware compatible con la comunidad que se usa para propagar encabezados HTTP de la solicitud entrante a las solicitudes de cliente HTTP salientes. Para utilizar la propagación de encabezados:
Haga referencia al puerto de comunidad admitido del paquete HeaderPropagation. ASP.NET Core 3.1 y las versiones posteriores admiten Microsoft.AspNetCore.HeaderPropagation.
Configure el middleware y
HttpClient
enStartup
:public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseMvc(); }
El cliente incluye los encabezados configurados en las solicitudes salientes:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);