Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este artículo, aprenderá a usar la interfaz de IHttpClientFactory para crear HttpClient tipos con varios aspectos básicos de .NET, como la inyección de dependencias (DI), el registro de eventos 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. se usa para hacer solicitudes HTTP y controlar las respuestas HTTP de los recursos web identificados por un . 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, actúa como una abstracción de fábrica que puede crear instancias de con configuraciones personalizadas. IHttpClientFactory se introdujo en .NET Core 2.1. Las cargas de trabajo .NET comunes basadas en HTTP pueden aprovechar con facilidad el middleware de terceros enfocado en el manejo de fallos transitorios y resilientes.
Advertencia
Si la aplicación requiere cookies, se recomienda evitar el uso de . La agrupación de las instancias de da como resultado el uso compartido de objetos . El uso compartido de no anticipado podría filtrar cookies entre partes no relacionadas de la aplicación. Además, cuando expira, el controlador se recicla, lo que significa que todas las cookies almacenadas en su se pierden. Para obtener formas alternativas de administrar clientes, consulte Directrices para usar clientes HTTP.
Importante
La gestión del ciclo de vida de las instancias de creadas por es completamente diferente a la de las instancias creadas manualmente. Las estrategias son usar clientes de corta duración creados por o clientes de larga duración con 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
Todo el código fuente de ejemplo proporcionado en este artículo requiere la instalación del paquete de NuGet . Además, los ejemplos de código muestran el uso de solicitudes HTTP para recuperar objetos de usuario de la API gratuita de JSON Placeholder.
Cuando llama a cualquiera de los métodos de extensión , está agregando y los servicios y relacionados a . El tipo ofrece las ventajas siguientes:
- Expone la clase como tipo listo para la inserción de dependencias.
- Proporciona una ubicación central para denominar y configurar instancias de lógicas.
- Codifica el concepto de middleware de salida a través de la delegación de controladores en .
- Proporciona métodos de extensión para el middleware basado en Polly a fin de aprovechar los controladores de delegación en .
- Administra el almacenamiento en caché y el ciclo de vida de las instancias de 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 de forma manual.
- Agrega una experiencia de registro configurable (a través de ) en todas las solicitudes enviadas a través de los clientes creados por Factory.
Patrones de consumo
se puede usar de varias formas en una aplicación:
- Uso básico
- Clientes con nombre
- Clientes tipados
- Clientes generados
El mejor enfoque depende de los requisitos de la aplicación.
Uso básico
Para registrar , llame a :
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 como parámetro de constructor con la inserción de dependencias. En el código siguiente se usa para crear una instancia de :
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
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 como en el ejemplo anterior es una buena manera de refactorizar una aplicación existente. No tiene efecto alguno en la forma de usar . En aquellos sitios de una aplicación existente en los que ya se hayan creado instancias de , reemplace esas repeticiones por llamadas a .
Clientes con nombre
Los clientes con nombre son una buena opción cuando:
- La aplicación requiere muchos usos distintos de .
- Muchas instancias de tienen otras configuraciones.
La configuración de un objeto con nombre se puede realizar durante la fase de registro en :
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 .
- La dirección base. .
- Encabezado de .
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 Configuration en .NET.
Nota
No se debe delimitar el número de clientes con nombre registrados distintos, ya que podría provocar el agotamiento de recursos. Por ejemplo, no derive el nombre de cliente de la entrada sin restricciones.
Crear el cliente
Cada vez que se llama a :
- Se crea una instancia de .
- Se llama a la acción de configuración.
Para crear un cliente con nombre, pase su nombre a :
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"];
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, ya que se usa la dirección base configurada para el 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 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 tipado acepta un parámetro 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)
{
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 [];
}
}
En el código anterior:
- La configuración se establece cuando el cliente con tipo se agrega a la colección de servicios.
- 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 . Por ejemplo, el método encapsula el código para recuperar objetos específicos del usuario.
En el código siguiente se llama a 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, registra como servicio transitorio. Este registro usa un Factory Method para:
- Crea una instancia de .
- Cree una instancia de , pasando la instancia de 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 , el tipo debe tener un constructor que acepte como parámetro. Además, el tipo 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
se puede usar en combinación con bibliotecas de terceros, como Refit. Refit es una biblioteca 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 , usando para realizar las llamadas HTTP externas.
Considere el siguiente tipo :
namespace Shared;
public record class Todo(
int UserId,
int Id,
string Title,
bool Completed);
El ejemplo siguiente se basa en el paquete NuGet 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 que devuelve una instancia de .
- Declara un atributo 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 typed 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 de HTTP. también admite otros verbos HTTP; por ejemplo:
POSTPUTDELETEPATCH
Para obtener una lista completa de los verbos HTTP admitidos, vea . 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 :
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 :
- Serializa el parámetro en JSON mediante . Usa una instancia de para configurar el proceso de serialización.
- Crea una instancia de a fin de empaquetar el JSON serializado para enviarlo en el cuerpo de la solicitud de HTTP.
- Llama a para enviar el contenido de JSON a la URL especificada. Se trata de una URL relativa que se agrega a HttpClient.BaseAddress.
- Llama a para iniciar una excepción si el código de estado de la respuesta no indica que la operación se ha procesado correctamente.
también admite otros tipos de contenido. Por ejemplo, y . Para obtener una lista completa del contenido admitido, vea .
En el ejemplo siguiente se muestra una solicitud HTTP :
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 . El método llama a en lugar de .
En el ejemplo siguiente se muestra una solicitud HTTP :
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 llama a . Dado que las solicitudes HTTP normalmente no contienen ningún cuerpo, el método no proporciona una sobrecarga que acepta una instancia de .
Para obtener más información sobre el uso de verbos HTTP diferentes con , vea .
Gestión de la vida útil
Se devuelve una nueva instancia de cada vez que se llama a en el . Se crea una instancia de por nombre de cliente. La fábrica administra la duración de las instancias de .
almacena en caché las instancias de creadas por la fábrica para reducir el consumo de recursos. Cuando se crea una instancia de , se puede reutilizar una instancia de 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 para cada cliente en el cuando se registre en :
services.AddHttpClient("Named.Client")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Importante
Las instancias de creadas por están pensadas para ser de corta duración.
Reciclar y recrear cuando su vida útil expira es esencial para para garantizar que los manejadores reaccionen a los cambios de DNS. se vincula a una instancia de controlador específica cuando se crea, por lo que deben solicitarse nuevas instancias de a tiempo para asegurarse de que el cliente obtenga el controlador actualizado.
La eliminación de estas instancias de creadas por la fábrica no provocará el agotamiento de sockets, ya que su eliminación no desencadenará la eliminación de . hace un seguimiento y elimina los recursos que se usan para crear instancias de , específicamente las instancias de , en cuanto expira su duración y ya no hay ningún usándolas.
Mantener activa una única instancia de durante una larga duración es un patrón común que se puede usar como alternativa a . Sin embargo, este patrón requiere configuración adicional, como . Puede usar clientes de larga duración con o clientes de corta duración creados con . Para obtener información sobre qué estrategia usar en la aplicación, consulte Directrices para usar clientes HTTP.
Configuración de
Puede que sea necesario controlar la configuración del elemento interno usado por un cliente.
Se devuelve un cuando se agregan clientes con nombre o con tipo. El método de extensión se puede llamar en el y pasarlo en un delegado. Este delegado servirá para crear y configurar el elemento principal que ese cliente usa:
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
La configuración de permite especificar un proxy para la instancia entre otras diversas propiedades del controlador. Para obtener más información, consulte Proxy por cliente.
Configuración adicional
Hay varias opciones de configuración adicionales para controlar :
| Método | Descripción |
|---|---|
| AddHttpMessageHandler | Agrega un controlador de mensajes adicional para una clase con nombre. |
| AddTypedClient | Configura la vinculación entre el y el nombrado asociado con el . |
| ConfigureHttpClient | Agrega un delegado que se usará para configurar un objeto con nombre. |
| ConfigurePrimaryHttpMessageHandler | Configura la clase principal del contenedor de inserción de dependencias para una clase 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 . Cada cliente con nombre específico puede tener configurado su propio valor de duración del controlador. |
| UseSocketsHttpHandler | Configura una instancia nueva o agregada previamente a través del contenedor de inserción de dependencias que se usará como controlador principal para un objeto denominado . (solo .NET 5+ ) |
Uso junto con
La implementación SocketsHttpHandler de HttpMessageHandler se agregó en .NET Core 2.1, lo 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 se considera una alternativa al uso de . Para obtener más información, consulte Directrices para usar HttpClient.
Sin embargo, y se pueden usar juntos para mejorar la configuración. Al usar estas dos API, obtiene la ventaja de poder definir una configuración de bajo nivel (por ejemplo, usando para la selección de certificados dinámicos) y de alto nivel (por ejemplo, aprovechando la integración de dependencias (DI) y varias configuraciones de clientes).
Para usar ambas API:
- Especifique
SocketsHttpHandlercomoPrimaryHandlera través de ConfigurePrimaryHttpMessageHandler o UseSocketsHttpHandler (solo .NET 5+). - Configure en función del intervalo que espera que se actualice el DNS; por ejemplo, a un valor que ya se había establecido en el método de extensión .
- (Opcional) Dado que controlará el reciclaje y la agrupación de conexiones, ya no hay que realizar el reciclaje del controlador en el nivel de . Si desea deshabilitarla, establezca en .
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 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 y la sección Observaciones en la documentos de la API .
Evitar clientes con tipo en servicios singleton
Cuando se usa el enfoque de cliente con nombre, se inserta en los servicios y se crean instancias de llamando a cada vez que se necesita un .
Sin embargo, con el enfoque de cliente tipado, los clientes tipados 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 creadas por (para obtener más información, consulte Administración de la duración de ). Una vez que se crea una instancia de cliente tipada, 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 .
Si tiene que usar instancias de en un servicio singleton, tenga en cuenta las siguientes opciones:
- Use el enfoque de cliente con nombre en su lugar, que inserta en el servicio singleton y vuelve a crear instancias de cuando es necesario.
- Si necesita el enfoque de cliente con tipo, use con configurado como controlador principal. Para más información sobre el uso de con , consulte la sección Uso de IHttpClientFactory junto con SocketsHttpHandler.
Ámbitos del controlador de mensajes en
crea un ámbito de inserción de dependencias aparte por cada instancia de . Estos ámbitos de inserción de dependencias son independientes de los ámbitos de inserción de dependencias de la aplicación (por ejemplo, el ámbito de solicitud entrante de ASP.NET, o un ámbito de inserción de dependencias manual creado por el usuario), por lo que no compartirán instancias de servicios con ámbito. Los ámbitos del manejador de mensajes están vinculados a la duración del manejador y pueden extenderse más allá de los ámbitos de aplicación, lo que puede provocar, por ejemplo, reutilizar la misma instancia con las mismas dependencias de ámbito inyectadas entre varias solicitudes entrantes.
Diagrama que muestra dos ámbitos DI de aplicación y un ámbito separado para el controlador de mensajes
Se recomienda encarecidamente a los usuarios no almacenar en caché información relacionada con el ámbito (por ejemplo, datos de ) dentro de las instancias de 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 transitorio independiente y lo haría alrededor de una instancia de de la caché de . Para acceder a la llamada del controlador en cualquier cliente con nombre registrado. En ese caso, crearía una instancia de mediante el controlador construido.
Diagrama que muestra cómo obtener acceso a los ámbitos de inserción de dependencias de aplicaciones a través de un controlador de mensajes transitorio independiente y IHttpMessageHandlerFactory
En el ejemplo siguiente se muestra cómo crear un objeto con un objeto 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 compatible con el ámbito y anular el registro de 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.
Evite depender del controlador principal predeterminado de fábrica
En esta sección, el término controlador principal "predeterminado de fábrica" se refiere al controlador principal que la implementación de predeterminada (o más precisamente, la implementación de predeterminada) asigna si no se configura de ninguna manera.
Nota
El controlador principal "predeterminado de fábrica" es un detalle de implementación y está sujeto a cambios. Evite depender de una implementación específica que se utilice como "predeterminada de fábrica" (por ejemplo, ).
Hay casos en los que debe conocer el tipo específico de un Manejador Principal, especialmente si estás trabajando en una biblioteca de clases. Mientras conserva la configuración del usuario, es posible que quiera actualizar propiedades específicas de , como , y . Puede resultar tentador convertir el controlador principal en , lo cual funcionó cuando se utilizó como controlador principal "predeterminado de fábrica". Sin embargo, como cualquier código que dependa de los detalles de implementación, esta solución alternativa es frágil y es probable que falle.
En lugar de confiar en el controlador principal "factory-default", puede usar para configurar una instancia predeterminada del controlador principal de nivel de aplicación:
// Contract with the end-user: Only HttpClientHandler is supported.
// --- "Pre-configure" stage ---
// The default is fixed as HttpClientHandler to avoid depending on the "factory-default"
// Primary Handler.
services.ConfigureHttpClientDefaults(b =>
b.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { UseCookies = false }));
// --- "End-user" stage ---
// IHttpClientBuilder builder = services.AddHttpClient("test", /* ... */);
// ...
// --- "Post-configure" stage ---
// The code can rely on the contract, and cast to HttpClientHandler only.
builder.ConfigurePrimaryHttpMessageHandler((handler, provider) =>
{
if (handler is not HttpClientHandler h)
{
throw new InvalidOperationException("Only HttpClientHandler is supported");
}
h.ClientCertificates.Add(GetClientCert(provider, builder.Name));
//X509Certificate2 GetClientCert(IServiceProvider p, string name) { ... }
});
Como alternativa, puede considerar la posibilidad de comprobar el tipo de controlador primario y configurar las especificaciones, como los certificados de cliente, solo en los tipos auxiliares conocidos, que lo más probable es que sean y .
// --- "End-user" stage ---
// IHttpClientBuilder builder = services.AddHttpClient("test", /* ... */);
// ...
// --- "Post-configure" stage ---
// No contract is in place. Trying to configure main handler types supporting client
// certs, logging and skipping otherwise.
builder.ConfigurePrimaryHttpMessageHandler((handler, provider) =>
{
if (handler is HttpClientHandler h)
{
h.ClientCertificates.Add(GetClientCert(provider, builder.Name));
}
else if (handler is SocketsHttpHandler s)
{
s.SslOptions ??= new System.Net.Security.SslClientAuthenticationOptions();
s.SslOptions.ClientCertificates ??= new X509CertificateCollection();
s.SslOptions.ClientCertificates!.Add(GetClientCert(provider, builder.Name));
}
else
{
// Log warning
}
//X509Certificate2 GetClientCert(IServiceProvider p, string name) { ... }
});
Consulte también
- Problemas de uso comunes
- inyección de dependencias en .NET
- Logging in .NET
- Configuration en .NET
- IHttpClientFactory
- IHttpMessageHandlerFactory
- HttpClient
- Realización de solicitudes HTTP con HttpClient
- Implementación de reintentos HTTP con retroceso exponencial