IHttpClientFactory con .NET
En este artículo verá cómo usar la interfaz IHttpClientFactory
para crear tipos HttpClient
con varios aspectos básicos de .NET, como la inserción de dependencias (ID), los registros y la configuración. El tipo HttpClient se introdujo en .NET Framework 4.5, que se publicó en 2012. En otras palabras, ha existido durante un tiempo. HttpClient
se usa para hacer solicitudes HTTP y controlar las respuestas HTTP de los recursos web identificados por un Uri. El protocolo HTTP compone la mayor parte de todo el tráfico de Internet.
Dado que los principios modernos de desarrollo de aplicaciones rigen los procedimientos recomendados, IHttpClientFactory actúa como una abstracción de fábrica que puede crear instancias de HttpClient
con configuraciones personalizadas. IHttpClientFactory se presentó en .NET Core 2.1. Las cargas de trabajo de .NET comunes basadas en HTTP pueden aprovechar con facilidad el middleware de terceros resistente y con control de errores transitorios.
Nota
Si la aplicación requiere cookies, puede ser preferible evitar el uso de IHttpClientFactory en la aplicación. Para obtener formas alternativas de administrar clientes, consulte Directrices para usar clientes HTTP.
Importante
La administración de la duración de las instancias de HttpClient
creadas por IHttpClientFactory
es completamente diferente a la de las instancias creadas manualmente. Las estrategias son usar clientes de corta duración creados por IHttpClientFactory
o clientes de larga duración con PooledConnectionLifetime
configurado. Para obtener más información, consulte la sección Administración de la duración de HttpClient y Directrices para usar HttpClient.
El tipo IHttpClientFactory
Todo el código fuente de ejemplo proporcionado en este artículo requiere la instalación del paquete de NuGet Microsoft.Extensions.Http
. Además, los ejemplos de código muestran el uso de solicitudes GET
HTTP para recuperar objetos Todo
de usuario de la API gratuita de marcador de posición {JSON}.
Cuando llama a cualquiera de los métodos de extensión AddHttpClient, está agregando IHttpClientFactory
y los servicios y relacionados a IServiceCollection. El tipo IHttpClientFactory
ofrece las ventajas siguientes:
- Expone la clase
HttpClient
como tipo listo para la inserción de dependencias. - Proporciona una ubicación central para denominar y configurar instancias de
HttpClient
lógicas. - Codifica el concepto de middleware de salida a través de la delegación de controladores en
HttpClient
. - Proporciona métodos de extensión para el middleware basado en Polly a fin de aprovechar los controladores de delegación en
HttpClient
. - Administra el almacenamiento en caché y la duración de las instancias de HttpClientHandler subyacentes. La administración automática evita los problemas comunes del Sistema de nombres de dominio (DNS) que se producen al administrar la duración de
HttpClient
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.
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
Para registrar IHttpClientFactory
, llame a AddHttpClient
:
using Shared;
using BasicHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHttpClient();
builder.Services.AddTransient<TodoService>();
using IHost host = builder.Build();
El consumo de servicios puede requerir IHttpClientFactory
como parámetro de constructor con la inserción de dependencias. En el código siguiente se usa IHttpClientFactory
para crear una instancia de HttpClient
:
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;
namespace BasicHttp.Example;
public sealed class TodoService(
IHttpClientFactory httpClientFactory,
ILogger<TodoService> logger)
{
public async Task<Todo[]> GetUserTodosAsync(int userId)
{
// Create the client
using HttpClient client = httpClientFactory.CreateClient();
try
{
// Make HTTP GET request
// Parse JSON response deserialize into Todo types
Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
$"https://jsonplaceholder.typicode.com/todos?userId={userId}",
new JsonSerializerOptions(JsonSerializerDefaults.Web));
return todos ?? [];
}
catch (Exception ex)
{
logger.LogError("Error getting something fun to say: {Error}", ex);
}
return [];
}
}
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 de
HttpClient
tienen otras configuraciones.
La configuración de un objeto HttpClient
con nombre se puede realizar durante la fase de registro en IServiceCollection
:
using Shared;
using NamedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
string? httpClientName = builder.Configuration["TodoHttpClientName"];
ArgumentException.ThrowIfNullOrEmpty(httpClientName);
builder.Services.AddHttpClient(
httpClientName,
client =>
{
// Set the base address of the named client.
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
// Add a user-agent default request header.
client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
});
En el código anterior, el cliente está configurado con:
- Un nombre que se extrae de la configuración en
"TodoHttpClientName"
. - La dirección base.
https://jsonplaceholder.typicode.com/
. - Encabezado de
"User-Agent"
.
Puede usar la configuración para especificar nombres de cliente HTTP, lo que resulta útil para evitar errores de nomenclatura de clientes al agregar y crear. En este ejemplo, el archivo appsettings.json se usa para configurar el nombre de cliente HTTP:
{
"TodoHttpClientName": "JsonPlaceholderApi"
}
Es fácil ampliar esta configuración y almacenar más detalles sobre cómo quiere que funcione el cliente HTTP. Para obtener más información, vea Configuración en .NET.
Crear el cliente
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
:
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Shared;
namespace NamedHttp.Example;
public sealed class TodoService
{
private readonly IHttpClientFactory _httpClientFactory = null!;
private readonly IConfiguration _configuration = null!;
private readonly ILogger<TodoService> _logger = null!;
public TodoService(
IHttpClientFactory httpClientFactory,
IConfiguration configuration,
ILogger<TodoService> logger) =>
(_httpClientFactory, _configuration, _logger) =
(httpClientFactory, configuration, logger);
public async Task<Todo[]> GetUserTodosAsync(int userId)
{
// Create the client
string? httpClientName = _configuration["TodoHttpClientName"];
using HttpClient client = _httpClientFactory.CreateClient(httpClientName ?? "");
try
{
// Make HTTP GET request
// Parse JSON response deserialize into Todo type
Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
$"todos?userId={userId}",
new JsonSerializerOptions(JsonSerializerDefaults.Web));
return todos ?? [];
}
catch (Exception ex)
{
_logger.LogError("Error getting something fun to say: {Error}", ex);
}
return [];
}
}
En el código anterior, no es necesario especificar un nombre de host en la solicitud HTTP. 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.
- Ofrece 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:
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;
namespace TypedHttp.Example;
public sealed class TodoService(
HttpClient httpClient,
ILogger<TodoService> logger) : IDisposable
{
public async Task<Todo[]> GetUserTodosAsync(int userId)
{
try
{
// Make HTTP GET request
// Parse JSON response deserialize into Todo type
Todo[]? todos = await httpClient.GetFromJsonAsync<Todo[]>(
$"todos?userId={userId}",
new JsonSerializerOptions(JsonSerializerDefaults.Web));
return todos ?? [];
}
catch (Exception ex)
{
logger.LogError("Error getting something fun to say: {Error}", ex);
}
return [];
}
public void Dispose() => httpClient?.Dispose();
}
En el código anterior:
- La configuración se establece cuando el cliente con tipo se agrega a la colección de servicios.
HttpClient
se asigna como variable con ámbito de clase (campo) y se usa con las API expuestas.
Se pueden crear métodos específicos de la API que exponen la funcionalidad de HttpClient
. Por ejemplo, el método GetUserTodosAsync
encapsula el código para recuperar objetos Todo
específicos del usuario.
En el código siguiente se llama a AddHttpClient para registrar una clase de cliente con tipo:
using Shared;
using TypedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHttpClient<TodoService>(
client =>
{
// Set the base address of the typed client.
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
// Add a user-agent default request header.
client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
});
El cliente con tipo se registra como transitorio con inserción con dependencias, En el código anterior, AddHttpClient
registra TodoService
como servicio transitorio. Este registro usa un Factory Method para:
- Crea una instancia de
HttpClient
. - Cree una instancia de
TodoService
, pasando la instancia deHttpClient
a su constructor.
Importante
El uso de clientes con tipo en servicios singleton puede ser peligroso. Para más información, consulte la sección Evitar clientes con tipo en servicios singleton.
Nota:
Al registrar un cliente tipado con el método AddHttpClient<TClient>
, el tipo TClient
debe tener un constructor que acepte HttpClient
como parámetro. Además, el tipo TClient
no se debe registrar con el contenedor de inserción de dependencias por separado, ya que esto haría que el registro posterior sobrescribiera el anterior.
Clientes generados
IHttpClientFactory
se puede usar en combinación con bibliotecas de terceros, como Refit. Refit es una biblioteca de REST para .NET Permite definiciones de API REST declarativas, con lo que se asignan métodos de interfaz a puntos de conexión. Se genera una implementación de la interfaz dinámicamente por medio de RestService
, usando HttpClient
para realizar las llamadas HTTP externas.
Considere el siguiente tipo record
:
namespace Shared;
public record class Todo(
int UserId,
int Id,
string Title,
bool Completed);
El ejemplo siguiente se basa en el paquete NuGet Refit.HttpClientFactory
y es una interfaz sencilla:
using Refit;
using Shared;
namespace GeneratedHttp.Example;
public interface ITodoService
{
[Get("/todos?userId={userId}")]
Task<Todo[]> GetUserTodosAsync(int userId);
}
La interfaz de C# anterior:
- Define un método denominado
GetUserTodosAsync
que devuelve una instancia deTask<Todo[]>
. - Declara un atributo
Refit.GetAttribute
con la ruta de acceso y la cadena de consulta a la API externa.
Un cliente con tipo se puede agregar usando Refit para generar la implementación:
using GeneratedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Refit;
using Shared;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddRefitClient<ITodoService>()
.ConfigureHttpClient(client =>
{
// Set the base address of the named client.
client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
// Add a user-agent default request header.
client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
});
La interfaz definida se puede consumir cuando sea preciso, con la implementación proporcionada por la inserción de dependencias y Refit.
Realización de solicitudes POST, PUT y DELETE
En los ejemplos anteriores, todas las solicitudes HTTP usan el verbo GET
de HTTP. HttpClient
también admite otros verbos HTTP; por ejemplo:
POST
PUT
DELETE
PATCH
Para obtener una lista completa de los verbos HTTP admitidos, vea HttpMethod. Para obtener más información sobre cómo realizar solicitudes HTTP, consulte Envío de una solicitud mediante HttpClient.
En el ejemplo siguiente se muestra cómo hacer una solicitud HTTP POST
:
public async Task CreateItemAsync(Item item)
{
using StringContent json = new(
JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
Encoding.UTF8,
MediaTypeNames.Application.Json);
using HttpResponseMessage httpResponse =
await httpClient.PostAsync("/api/items", json);
httpResponse.EnsureSuccessStatusCode();
}
En el código anterior, el método CreateItemAsync
:
- Serializa el parámetro
Item
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, vea HttpContent.
En el ejemplo siguiente se muestra una solicitud HTTP PUT
:
public async Task UpdateItemAsync(Item item)
{
using StringContent json = new(
JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
Encoding.UTF8,
MediaTypeNames.Application.Json);
using HttpResponseMessage httpResponse =
await httpClient.PutAsync($"/api/items/{item.Id}", json);
httpResponse.EnsureSuccessStatusCode();
}
El código anterior es muy similar al del ejemplo de POST
. El método UpdateItemAsync
llama a PutAsync en lugar de PostAsync
.
En el ejemplo siguiente se muestra una solicitud HTTP DELETE
:
public async Task DeleteItemAsync(Guid id)
{
using HttpResponseMessage httpResponse =
await httpClient.DeleteAsync($"/api/items/{id}");
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
, vea HttpClient.
Administración de la duración de HttpClient
Cada vez que se llama a CreateClient
en IHttpClientFactory
, se devuelve una nueva instancia de HttpClient
. Se crea una instancia de HttpClientHandler por nombre de cliente. La fábrica administra la duración de las instancias de HttpClientHandler
.
IHttpClientFactory
almacena en caché las instancias de HttpClientHandler
creadas por la fábrica para reducir el consumo de recursos. Cuando se crea una instancia de HttpClient
, se puede reutilizar una instancia de HttpClientHandler
de la memoria caché si su duración aún no ha expirado.
Se recomienda almacenar en caché los controladores, porque cada uno de ellos suele administrar su propio grupo de conexiones HTTP subyacente. Crear más controladores de los necesarios puede provocar el agotamiento del socket y 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. Para invalidar el valor predeterminado, llame a SetHandlerLifetime para cada cliente, en IServiceCollection
:
services.AddHttpClient("Named.Client")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Importante
Las instancias de HttpClient
creadas por IHttpClientFactory
están pensadas para ser de corta duración.
El reciclaje y la recreación de las instancias de
HttpMessageHandler
cuando expira su duración es esencial para queIHttpClientFactory
se asegure de que los controladores reaccionan a los cambios de DNS.HttpClient
se vincula a una instancia de controlador específica cuando se crea, por lo que deben solicitarse nuevas instancias deHttpClient
a tiempo para asegurarse de que el cliente obtenga el controlador actualizado.La eliminación de estas instancias de
HttpClient
creadas por la fábrica no provocará el agotamiento de sockets, ya que su eliminación no desencadenará la eliminación deHttpMessageHandler
.IHttpClientFactory
hace un seguimiento y elimina los recursos que se usan para crear instancias deHttpClient
, específicamente las instancias deHttpMessageHandler
, en cuanto expira su duración y ya no hay ningúnHttpClient
usándolas.
Mantener activa una única instancia de HttpClient
durante una larga duración es un patrón común que se puede usar como alternativa a IHttpClientFactory
. Sin embargo, este patrón requiere configuración adicional, como PooledConnectionLifetime
. Puede usar clientes de larga duración con PooledConnectionLifetime
o clientes de corta duración creados con IHttpClientFactory
. Para obtener información sobre qué estrategia usar en la aplicación, consulte Directrices para usar clientes HTTP.
Configuración de 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 en IServiceCollection
. Este delegado servirá para crear y configurar el elemento principal HttpMessageHandler
que ese cliente usa:
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
La configuración de HttClientHandler
permite especificar un proxy para la instancia HttpClient
entre otras diversas propiedades del controlador. Para obtener más información, consulte Proxy HTTP.
Configuración adicional
Hay varias opciones de configuración adicionales para controlar IHttpClientHandler
:
Método | Descripción |
---|---|
AddHttpMessageHandler | Agrega un controlador de mensajes adicional para una clase HttpClient con nombre. |
AddTypedClient | Configura el enlace entre el tipo TClient y la clase HttpClient con nombre asociada a la interfaz IHttpClientBuilder . |
ConfigureHttpClient | Agrega un delegado que se usará para configurar un objeto HttpClient con nombre. |
ConfigurePrimaryHttpMessageHandler | Configura la clase HttpMessageHandler principal del contenedor de inserción de dependencias para una clase HttpClient con nombre. |
RedactLoggedHeaders | Establece la colección de nombres de encabezado HTTP para los que se deben censurar los valores antes del registro. |
SetHandlerLifetime | Establece el período de tiempo que se puede volver a usar una instancia de HttpMessageHandler . Cada cliente con nombre puede tener configurado su propio valor de duración de controlador. |
UseSocketsHttpHandler | Configura una instancia SocketsHttpHandler nueva o agregada previamente a través del contenedor de inserción de dependencias que se usará como controlador principal para un objeto denominado HttpClient . (solo .NET 5+) |
Uso de IHttpClientFactory junto con SocketsHttpHandler
La implementación de HttpMessageHandler
con SocketsHttpHandler
se agregó en .NET Core 2.1, que permite configurar PooledConnectionLifetime
. Esta configuración se usa para asegurarse de que el controlador reacciona a los cambios de DNS, por lo que el uso de SocketsHttpHandler
se considera una alternativa al uso de IHttpClientFactory
. Para obtener más información, consulte Directrices para usar HttpClient.
Sin embargo, se pueden usar SocketsHttpHandler
y IHttpClientFactory
conjuntamente para mejorar la capacidad de configuración. Al usar las dos API, obtiene la ventaja de poder definir una configuración de bajo nivel (por ejemplo, usando LocalCertificateSelectionCallback
para la selección de certificados dinámicos) y de alto nivel (por ejemplo, aprovechando la integración de la inserción de dependencias y varias configuraciones de cliente).
Para usar ambas API:
- Indique
SocketsHttpHandler
comoPrimaryHandler
a través de ConfigurePrimaryHttpMessageHandler o UseSocketsHttpHandler (solo .NET 5+). - Configure SocketsHttpHandler.PooledConnectionLifetime en función del intervalo que prevea que vaya a actualizar el DNS; por ejemplo, según un valor que se encontraba anteriormente en
HandlerLifetime
. - (Opcional) Dado que
SocketsHttpHandler
controlará el reciclaje y la agrupación de conexiones, ya no hay que realizar el reciclaje del controlador en el nivel deIHttpClientFactory
. Si desea deshabilitarla, establezcaHandlerLifetime
enTimeout.InfiniteTimeSpan
.
services.AddHttpClient(name)
.UseSocketsHttpHandler((handler, _) =>
handler.PooledConnectionLifetime = TimeSpan.FromMinutes(2)) // Recreate connection every 2 minutes
.SetHandlerLifetime(Timeout.InfiniteTimeSpan); // Disable rotation, as it is handled by PooledConnectionLifetime
En el ejemplo anterior, se eligieron 2 minutos de forma arbitraria con fines ilustrativos, que se alinean con un valor HandlerLifetime
predeterminado. Debe elegir el valor en función de la frecuencia esperada del DNS u otros cambios de red. Para obtener más información, consulte la sección Funcionamiento de DNS en las reglas generales de HttpClient
y la sección Observaciones en la documentos de la API PooledConnectionLifetime.
Evitar clientes con tipo en servicios singleton
Cuando se usa el enfoque de cliente con nombre, se inserta IHttpClientFactory
en los servicios y se crean instancias de HttpClient
llamando a CreateClient cada vez que se necesita un HttpClient
.
Sin embargo, con el enfoque de cliente con tipo, los clientes con tipo son objetos transitorios que normalmente se insertan en los servicios. Esto puede causar un problema, porque un cliente con tipo se puede insertar en un servicio singleton.
Importante
Se espera que los clientes con tipo sean de corta duración en el mismo sentido que las instancias de HttpClient
creadas por IHttpClientFactory
(para obtener más información, consulte Administración de la duración de HttpClient
). En cuanto se crea una instancia de cliente con tipo, IHttpClientFactory
no tiene control sobre ella. Si una instancia de cliente con tipo se captura en un servicio singleton, puede impedir que reaccione a los cambios de DNS, lo que anularía uno de los propósitos de IHttpClientFactory
.
Si tiene que usar instancias de HttpClient
en un servicio singleton, tenga en cuenta las siguientes opciones:
- Use el enfoque de cliente con nombre en su lugar, que inserta
IHttpClientFactory
en el servicio singleton y vuelve a crear instancias deHttpClient
cuando es necesario. - Si necesita el enfoque de cliente con tipo, use
SocketsHttpHandler
conPooledConnectionLifetime
configurado como controlador principal. Para más información sobre el uso deSocketsHttpHandler
conIHttpClientFactory
, consulte la sección Uso de IHttpClientFactory junto con SocketsHttpHandler.
Ámbitos del controlador de mensajes en IHttpClientFactory
IHttpClientFactory
crea un ámbito de inserción de dependencias aparte por cada instancia de HttpMessageHandler
. Estos ámbitos de inserción de dependencias son independientes de los de la aplicación (por ejemplo, un ámbito de solicitudes entrantes de ASP.NET o un ámbito de inserción de dependencias manual creado por el usuario), por lo que no compartirán instancias de servicio con ámbito. Los ámbitos de controlador de mensajes están vinculados a la duración del controlador y pueden sobrevivir a los ámbitos de la aplicación, lo que puede provocar, por ejemplo, la reutilización de la misma instancia de HttpMessageHandler
con las mismas dependencias con ámbito insertadas entre varias solicitudes entrantes.
Se recomienda encarecidamente a los usuarios no almacenar en caché información relacionada con el ámbito (por ejemplo, datos de HttpContext
) dentro de las instancias de HttpMessageHandler
y usar las dependencias con ámbito con precaución para evitar la filtración de información confidencial.
Si necesitara acceso a un ámbito de inserción de dependencias de aplicaciones desde el controlador de mensajes (por ejemplo, para la autenticación), encapsularía la lógica compatible con el ámbito en un objeto DelegatingHandler
transitorio independiente y lo haría alrededor de una instancia de HttpMessageHandler
de la caché de IHttpClientFactory
. Para acceder a la llamada IHttpMessageHandlerFactory.CreateHandler del controlador en cualquier cliente con nombre registrado. En ese caso, crearía una instancia de HttpClient
mediante el controlador construido.
En el ejemplo siguiente se muestra cómo crear un objeto HttpClient
con un objeto DelegatingHandler
compatible con el ámbito:
if (scopeAwareHandlerType != null)
{
if (!typeof(DelegatingHandler).IsAssignableFrom(scopeAwareHandlerType))
{
throw new ArgumentException($"""
Scope aware HttpHandler {scopeAwareHandlerType.Name} should
be assignable to DelegatingHandler
""");
}
// Create top-most delegating handler with scoped dependencies
scopeAwareHandler = (DelegatingHandler)_scopeServiceProvider.GetRequiredService(scopeAwareHandlerType); // should be transient
if (scopeAwareHandler.InnerHandler != null)
{
throw new ArgumentException($"""
Inner handler of a delegating handler {scopeAwareHandlerType.Name} should be null.
Scope aware HttpHandler should be registered as Transient.
""");
}
}
// Get or create HttpMessageHandler from HttpClientFactory
HttpMessageHandler handler = _httpMessageHandlerFactory.CreateHandler(name);
if (scopeAwareHandler != null)
{
scopeAwareHandler.InnerHandler = handler;
handler = scopeAwareHandler;
}
HttpClient client = new(handler);
Una solución adicional puede ser usar un método de extensión para registrar un objeto DelegatingHandler
compatible con el ámbito y anular el registro de IHttpClientFactory
predeterminado mediante un servicio transitorio con acceso al ámbito actual de la aplicación:
public static IHttpClientBuilder AddScopeAwareHttpHandler<THandler>(
this IHttpClientBuilder builder) where THandler : DelegatingHandler
{
builder.Services.TryAddTransient<THandler>();
if (!builder.Services.Any(sd => sd.ImplementationType == typeof(ScopeAwareHttpClientFactory)))
{
// Override default IHttpClientFactory registration
builder.Services.AddTransient<IHttpClientFactory, ScopeAwareHttpClientFactory>();
}
builder.Services.Configure<ScopeAwareHttpClientFactoryOptions>(
builder.Name, options => options.HttpHandlerType = typeof(THandler));
return builder;
}
Para obtener más información, vea el ejemplo completo.