Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Dans cet article, vous allez apprendre à utiliser l’interface IHttpClientFactory pour créer des types HttpClient avec différents principes fondamentaux de .NET, comme l’injection de dépendances (DI), la journalisation et la configuration. Le type HttpClient a été introduit dans .NET Framework 4.5, qui a été publié en 2012. En d’autres termes, il existe déjà depuis un moment.
HttpClient est utilisé pour effectuer des requêtes HTTP et gérer les réponses HTTP à partir de ressources web identifiées par un Uri. Le protocole HTTP couvre la grande majorité de tout le trafic Internet.
Avec les principes de développement d’applications modernes encourageant les meilleures pratiques, le IHttpClientFactory sert d’abstraction d’usine qui peut créer des instances HttpClient avec des configurations personnalisées.
IHttpClientFactory a été introduit dans .NET Core 2.1. Les charges de travail .NET courantes basées sur HTTP peuvent tirer facilement parti des intergiciels tiers de gestion des erreurs temporaires et résilients.
Avertissement
Si votre application nécessite des cookies, il est recommandé d’éviter d’utiliser IHttpClientFactory. Le regroupement des instances HttpMessageHandler entraîne le partage d’objets CookieContainer. Le partage imprévu CookieContainer peut fuiter des cookies entre des parties non liées de l’application. De plus, lorsque HandlerLifetime expire, le gestionnaire est recyclé, ce qui signifie que tous les cookies stockés dans son CookieContainer sont perdus. Pour obtenir d’autres méthodes de gestion des clients, consultez Recommandations pour l’utilisation des clients HTTP.
Importante
La gestion de la durée de vie des instances HttpClient créées par IHttpClientFactory est complètement différente des instances créées manuellement. Les stratégies sont d’utiliser des clients de courte durée créés par IHttpClientFactory ou des clients de longue durée avec configuration de PooledConnectionLifetime. Pour plus d’informations, consultez la section Gestion de la durée de vie de HttpClient et Instructions pour l’utilisation de clients HTTP.
Le type IHttpClientFactory
Tous les exemples de code source fournis dans cet article nécessitent l'installation du package Microsoft.Extensions.Http NuGet. En outre, les exemples de code démontrent l'utilisation de requêtes HTTP GET pour récupérer des objets utilisateur Todo à partir de l'API libre {JSON} Placeholder.
Lorsque vous appelez l’une des méthodes d’extension AddHttpClient, vous ajoutez le IHttpClientFactory et les services associés au IServiceCollection. Le type IHttpClientFactory offre les avantages suivants :
- Expose la classe
HttpClientsous la forme d’un type prêt pour l’intégration de données. - Fournit un emplacement central pour le nommage et la configuration d’instance de
HttpClientlogiques. - Codifie le concept d’intergiciel (middleware) sortant via la délégation de gestionnaires dans
HttpClient. - Fournit des méthodes d’extension pour les intergiciels basés sur Polly afin de tirer parti de la délégation de gestionnaires dans
HttpClient. - Gère la mise en cache et la durée de vie des instances HttpClientHandler sous-jacentes. La gestion automatique évite les problèmes courants liés au système DNS (Domain Name System) qui se produisent lors de la gestion manuelle des durées de vie
HttpClient. - Ajoute une expérience de journalisation configurable (via ILogger) pour toutes les requêtes envoyées via des clients créés par la fabrique.
Modèles de consommation
Vous pouvez utiliser IHttpClientFactory dans une application de plusieurs façons :
La meilleure approche dépend des exigences de l’application.
Utilisation de base
Pour enregistrer le IHttpClientFactory, appelez le 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();
La consommation de services peut nécessiter le IHttpClientFactory en tant que paramètre de constructeur avec DI. Le code suivant utilise IHttpClientFactory pour créer une instance 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
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 [];
}
}
L’utilisation de IHttpClientFactory comme dans l’exemple précédent est un bon moyen de refactoriser une application existante. Cela n’a aucun impact sur la façon dont HttpClient est utilisé. Aux endroits où des instances HttpClient sont créées dans une application existante, remplacez ces occurrences par des appels à CreateClient.
Clients nommés
Les clients nommés sont un bon choix dans les cas suivants :
- L’application nécessite de nombreuses utilisations distinctes de
HttpClient. - De nombreuses instances de
HttpClientont des configurations différentes.
La configuration d’un HttpClient nommé peut être spécifiée lors de l’inscription dans 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");
});
Dans le code précédent, le client est configuré avec :
- Nom extrait de la configuration sous le
"TodoHttpClientName". - L’adresse de base
https://jsonplaceholder.typicode.com/. - En-tête de
"User-Agent".
Vous pouvez utiliser la configuration pour spécifier des noms de clients HTTP, ce qui est utile pour éviter les erreurs de nommage des clients lors de l’ajout et de la création. Dans cet exemple, le fichier appsettings.json est utilisé pour configurer le nom du client HTTP :
{
"TodoHttpClientName": "JsonPlaceholderApi"
}
Il est facile d’étendre cette configuration et de stocker plus de détails sur le fonctionnement de votre client HTTP. Pour plus d’informations, consultez Configuration dans .NET.
Créer un client
Chaque fois que CreateClient est appelé :
- Une nouvelle instance de
HttpClientest créée. - L’action de configuration est appelée.
Pour créer un client nommé, passez son nom dans 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"];
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 [];
}
}
Dans le code précédent, la requête HTTP n’a pas besoin de spécifier un nom d’hôte. Le code peut simplement passer le chemin, car l’adresse de base configurée pour le client est utilisée.
Clients typés
Clients typés :
- Fournissent les mêmes fonctionnalités que les clients nommés, sans qu’il soit nécessaire d’utiliser des chaînes comme clés.
- Fournissez IntelliSense et l’aide du compilateur lors de la consommation de clients.
- Fournir un emplacement unique pour configurer et interagir avec un
HttpClientparticulier. Par exemple, un client typé unique peut être utilisé :- Pour un point de terminaison principal unique.
- Pour encapsuler toute la logique traitant du point de terminaison.
- Pour travailler avec l’injection de dépendances et injecter là où c’est nécessaire dans l’application.
Un client typé accepte un paramètre HttpClient dans son constructeur :
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 [];
}
}
Dans le code précédent :
- La configuration est définie lorsque le client typé est ajouté à la collection de services.
- Le
HttpClientest défini en tant que variable (champ) à portée de classe et est utilisé avec des API exposées.
Vous pouvez créer des méthodes spécifiques à l’API qui exposent des fonctionnalités de HttpClient. Par exemple, la méthode GetUserTodosAsync encapsule du code pour récupérer des objets Todo spécifiques à l’utilisateur.
Le code suivant appelle AddHttpClient pour inscrire une classe cliente typée :
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");
});
Le client typé est inscrit comme étant transitoire avec injection de dépendances. Dans le code précédent, AddHttpClient inscrit TodoService en tant que service temporaire. Cette inscription utilise une méthode de fabrique pour :
- Créez une instance de
HttpClient. - Créer une instance de
TodoServiceen passant l’instance deHttpClientà son constructeur.
Importante
L’utilisation de clients typés dans les services singleton peut être dangereuse. Pour plus d’informations, consultez la section Éviter les clients typés dans les services singleton.
Remarque
Lors de l’enregistrement d’un client typé avec la méthode AddHttpClient<TClient>, le type TClient doit avoir un constructeur qui accepte un HttpClient en tant que paramètre. De plus, le type TClient ne doit pas être enregistré séparément dans le conteneur DI, car cela entraînerait le remplacement de l’enregistrement précédent par le suivant.
Clients générés
IHttpClientFactory peut être utilisé en combinaison avec des bibliothèques tierces, comme Refit. Refit est une bibliothèque REST pour .NET. Cela permet des définitions d’API REST déclaratives, en mappant les méthodes d’interface aux points de terminaison. Une implémentation de l’interface est générée dynamiquement par le RestService, avec HttpClient pour faire les appels HTTP externes.
Considérez le type de record suivant :
namespace Shared;
public record class Todo(
int UserId,
int Id,
string Title,
bool Completed);
L’exemple suivant s’appuie sur le package NuGet Refit.HttpClientFactory et est une interface simple :
using Refit;
using Shared;
namespace GeneratedHttp.Example;
public interface ITodoService
{
[Get("/todos?userId={userId}")]
Task<Todo[]> GetUserTodosAsync(int userId);
}
L’interface C# précédente :
- Définit une méthode nommée
GetUserTodosAsyncqui retourne une instance deTask<Todo[]>. - Déclare un attribut
Refit.GetAttributeavec le chemin d’accès et la chaîne de requête à l’API externe.
Vous pouvez ajouter un client typé en utilisant Refit pour générer l’implémentation :
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");
});
L'interface définie peut être utilisée lorsque cela est nécessaire, avec l'implémentation fournie par DI et Refit.
Effectuer des requêtes POST, PUT et DELETE
Dans les exemples précédents, toutes les requêtes HTTP utilisent le verbe HTTP GET.
HttpClient prend également en charge d’autres verbes HTTP, notamment :
POSTPUTDELETEPATCH
Pour obtenir la liste complète des verbes HTTP pris en charge, consultez HttpMethod. Pour plus d’informations sur l’envoi de requêtes HTTP, consultez Envoyer une requête à l’aide de HttpClient.
L’exemple suivant montre comment effectuer une requête 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();
}
Dans le code précédent, la méthode CreateItemAsync :
- Sérialise le paramètre
Itemau format JSON à l’aide deSystem.Text.Json. Cela utilise une instance de JsonSerializerOptions pour configurer le processus de sérialisation. - Crée une instance de StringContent pour empaqueter le JSON sérialisé pour l’envoi dans le corps de la requête HTTP.
- Appelle PostAsync pour envoyer le contenu JSON à l’URL spécifiée. Il s’agit d’une URL relative qui est ajoutée à HttpClient.BaseAddress.
- Appelle EnsureSuccessStatusCode pour lever une exception si le code d’état de la réponse n’indique pas la réussite.
HttpClient prend également en charge d’autres types de contenu. Par exemple : MultipartContent et StreamContent. Pour obtenir la liste complète du contenu pris en charge, consultez HttpContent.
L’exemple suivant montre une requête 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();
}
Le code précédent est très similaire à l’exemple POST. La méthode UpdateItemAsync appelle PutAsync au lieu de PostAsync.
L’exemple suivant montre une requête HTTP DELETE :
public async Task DeleteItemAsync(Guid id)
{
using HttpResponseMessage httpResponse =
await httpClient.DeleteAsync($"/api/items/{id}");
httpResponse.EnsureSuccessStatusCode();
}
Dans le code précédent, la méthode DeleteItemAsync appelle DeleteAsync. Étant donné que les requêtes HTTP DELETE ne contiennent généralement aucun corps, la DeleteAsync méthode ne fournit pas de surcharge qui accepte une instance de HttpContent.
Pour en savoir plus sur l’utilisation de différents verbes HTTP avec HttpClient, consultez HttpClient.
Gestion de la durée de vie HttpClient
Une nouvelle instance HttpClient est retournée à chaque fois que CreateClient est appelé sur IHttpClientFactory. Une instance HttpClientHandler est créée par nom de client. La fabrique gère les durées de vie des instances HttpClientHandler.
IHttpClientFactory met en cache les instances de HttpClientHandler créées par la fabrique pour réduire la consommation des ressources. Une instance de HttpClientHandler peut être réutilisée à partir du cache quand vous créez une instance de HttpClient si sa durée de vie n’a pas expiré.
La mise en cache des gestionnaires est souhaitable, car chaque gestionnaire gère typiquement son propre pool de connexions HTTP sous-jacent. La création de plus de gestionnaires que nécessaire peut entraîner des épuisements de sockets et des délais de connexion. Certains gestionnaires conservent aussi les connexions indéfiniment ouvertes, ce qui peut empêcher le gestionnaire de réagir aux changements du DNS.
La durée de vie par défaut d’un gestionnaire est de deux minutes. Pour remplacer la valeur par défaut, appelez SetHandlerLifetime pour chaque client, sur le IHttpClientBuilder lors de l’inscription de IHttpClientFactory dans le ServiceCollection.
services.AddHttpClient("Named.Client")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Importante
Les instances HttpClient créées par IHttpClientFactory sont destinées à être de courte durée.
Le recyclage et la recréation des
HttpMessageHandlerquand leur durée de vie expire sont essentiels pourIHttpClientFactorygarantir que les gestionnaires réagissent aux modifications du DNS.HttpClientétant lié à une instance de gestionnaire spécifique lors de sa création, de nouvelles instancesHttpClientdoivent être demandées en temps opportun pour garantir que le client obtiendra le gestionnaire mis à jour.La suppression de telles instances
HttpClientcréées par l'usine n’entraîne pas d’épuisement du socket, puisque sa suppression ne déclenche pas l’élimination duHttpMessageHandler.IHttpClientFactoryeffectue le suivi et l’élimination des ressources utilisées pour créer des instancesHttpClient, en particulier les instancesHttpMessageHandler, dès que leur durée de vie expire et qu’il n’y a plus deHttpClientles utilisant.
Le maintien d’une instance unique de HttpClient en vie pendant une longue durée est un modèle courant qui peut être utilisé comme alternative à IHttpClientFactory. Cependant, ce modèle nécessite une configuration supplémentaire, comme PooledConnectionLifetime. Vous pouvez utiliser des clients de longue durée avec PooledConnectionLifetime, ou des clients à courte durée créés par IHttpClientFactory. Pour plus d’informations sur la stratégie à utiliser dans votre application, consultez Recommandations pour l’utilisation des clients HTTP.
Configurer le HttpMessageHandler
Il peut être nécessaire de contrôler la configuration du HttpMessageHandler interne utilisé par un client.
Un IHttpClientBuilder est retourné lors de l'ajout de clients nommés ou typés. La méthode d'extension ConfigurePrimaryHttpMessageHandler peut être appelée avec le IHttpClientBuilder et recevoir un délégué. Le délégué est utilisé pour créer et configurer le HttpMessageHandler principal utilisé par ce client :
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
La configuration de HttpClientHandler vous permet de spécifier un proxy pour l’instance HttpClient entre autres propriétés du gestionnaire. Pour plus d’informations, consultez Proxy par client.
Configuration supplémentaire
Il existe plusieurs options de configuration supplémentaires pour contrôler le IHttpClientBuilder :
| Méthode | Descriptif |
|---|---|
| AddHttpMessageHandler | Ajoute un gestionnaire de messages supplémentaire pour un HttpClient nommé. |
| AddTypedClient | Configure la liaison entre le TClient et le HttpClient nommé associé à IHttpClientBuilder. |
| ConfigureHttpClient | Ajoute un délégué utilisé pour configurer un HttpClient nommé. |
| ConfigurePrimaryHttpMessageHandler | Configure le HttpMessageHandler principal à partir du conteneur d’injection de dépendances pour un HttpClient nommé. |
| RedactLoggedHeaders | Définit la collection de noms d’en-têtes HTTP pour lesquels les valeurs doivent être modifiées avant la journalisation. |
| SetHandlerLifetime | Définit la durée pendant laquelle une instance de HttpMessageHandler peut être réutilisée. Chaque client nommé peut avoir sa propre valeur de durée de vie de gestionnaire configurée. |
| UseSocketsHttpHandler | Configure une nouvelle instance ou une instance précédemment ajoutée de SocketsHttpHandler à partir du conteneur d’injection de dépendances pour qu’elle soit utilisée comme gestionnaire principal pour un HttpClient nommé. (Seulement dans .NET 5+) |
Utilisation IHttpClientFactory avec SocketsHttpHandler
L’implémentation SocketsHttpHandler de HttpMessageHandler a été ajoutée dans .NET Core 2.1, ce qui permet à PooledConnectionLifetime d’être configuré. Ce paramètre est utilisé pour garantir que le gestionnaire réagit aux modifications DNS. L’utilisation de SocketsHttpHandler est donc considérée comme une alternative à l’utilisation de IHttpClientFactory. Pour plus d’informations, consultez Instructions relatives à l’utilisation de clients HTTP.
Toutefois, SocketsHttpHandler et IHttpClientFactory peut être utilisé ensemble pour améliorer la configuration. En utilisant ces deux API, vous pouvez configurer à la fois à un bas niveau (par exemple, en utilisant LocalCertificateSelectionCallback pour la sélection dynamique de certificats) et à un haut niveau (par exemple, en tirant parti de l'intégration de l'injection de dépendances (DI) et de plusieurs configurations clientes).
Pour utiliser les deux API :
- Spécifiez
SocketsHttpHandlercommePrimaryHandlervia ConfigurePrimaryHttpMessageHandler, ou UseSocketsHttpHandler (seulement dans .NET 5+). - Configurez SocketsHttpHandler.PooledConnectionLifetime en fonction de l'intervalle auquel vous vous attendez pour la mise à jour du DNS ; par exemple, pour une valeur précédemment définie dans la méthode d’extension
SetHandlerLifetime. - (Facultatif) Puisque
SocketsHttpHandlergérera le regroupement et le recyclage des connexions, le recyclage du gestionnaire au niveauIHttpClientFactoryn’est plus nécessaire. Vous pouvez le désactiver en définissantHandlerLifetimesurTimeout.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
Dans l’exemple ci-dessus, 2 minutes ont été choisies arbitrairement à des fins d’illustration, en s’alignant sur une valeur par défaut de HandlerLifetime. Vous devez choisir la valeur en fonction de la fréquence attendue du DNS ou d’autres modifications apportées au réseau. Pour plus d’informations, veuillez consulter la section comportement DNS dans les directives HttpClient, et la section Remarques dans la documentation de l’API PooledConnectionLifetime.
Éviter les clients typés dans les services singleton
Lorsque vous utilisez l’approche de client nommé, IHttpClientFactory est injecté dans les services et des instances HttpClient sont créées en appelant CreateClient chaque fois qu’un HttpClient est nécessaire.
Toutefois, avec l’approche de client typé, les clients typés sont des objets temporaires généralement injectés dans les services. Cela peut causer un problème, car un client typé peut être injecté dans un service singleton.
Importante
Les clients typés sont censés être de courte durée dans le même sens que les instances HttpClient créées par IHttpClientFactory (pour plus d’informations, consultez Gestion de la durée de vie de HttpClient). Dès qu’une instance de client typé est créée, IHttpClientFactory n’a aucun contrôle sur celle-ci. Si une instance de client typée est capturée dans un singleton, elle peut l’empêcher de réagir aux modifications DNS, ce qui va à l’encontre de l’un des objectifs de IHttpClientFactory.
Si vous devez utiliser des instances HttpClient dans un service singleton, envisagez les options suivantes :
- Utilisez plutôt l’approche de client nommé, en injectant
IHttpClientFactorydans le service singleton et en recréant des instancesHttpClientsi nécessaire. - Si vous avez besoin de l’approche de client typé, utilisez
SocketsHttpHandleravec lePooledConnectionLifetimeconfiguré comme gestionnaire principal. Pour plus d’informations sur l’utilisation deSocketsHttpHandleravecIHttpClientFactory, consultez la section Utilisation de IHttpClientFactory avec SocketsHttpHandler.
Étendues du gestionnaire de messages dans IHttpClientFactory
IHttpClientFactory crée une étendue d’ID distincte pour chaque instance HttpMessageHandler. Ces étendues d’ID sont distinctes des étendues d’injection de dépendances d’application (par exemple, étendue de requête entrante ASP.NET ou étendue d’injection de dépendances manuelle créée par l’utilisateur), ainsi elles ne partagent pas d’instances de service délimitées. Les étendues du gestionnaire de messages sont liées à la durée de vie du gestionnaire et peuvent survivre aux étendues d’application, ce qui peut entraîner, par exemple, la réutilisation de la même HttpMessageHandler instance avec les mêmes dépendances d’étendue injectées entre plusieurs requêtes entrantes.
Il est vivement recommandé aux utilisateurs de ne pas mettre en cache les informations liées à l’étendue (comme les données de HttpContext) à l’intérieur des instances HttpMessageHandler et d’utiliser les dépendances délimitées avec précaution pour éviter les fuites d’informations sensibles.
Si vous avez besoin d’accéder à une étendue d’injection de dépendances d’application à partir de votre gestionnaire de messages, par exemple, pour l’authentification, vous devez encapsuler une logique prenant en charge l’étendue dans un DelegatingHandler temporaire distinct et le wrapper autour d’une instance HttpMessageHandler du cache IHttpClientFactory. Pour accéder au gestionnaire, appelez IHttpMessageHandlerFactory.CreateHandler pour tout client nommé inscrit. Dans ce cas, vous devez créer une instance HttpClient vous-même en utilisant le gestionnaire construit.
L’exemple suivant montre la création d’un HttpClient avec un DelegatingHandler prenant en charge l’étendue :
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);
Une autre solution de contournement peut utiliser une méthode d’extension pour inscrire un DelegatingHandler prenant en charge l’étendue et remplacer l’inscription par défaut IHttpClientFactory par un service temporaire ayant accès à l’étendue d’application actuelle :
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;
}
Pour plus d’informations, consultez l’exemple complet.
Évitez de dépendre du gestionnaire principal « factory-default »
Dans cette section, le terme gestionnaire principal « factory-default » fait référence au gestionnaire principal que l’implémentation IHttpClientFactory par défaut (ou plus précisément, l’implémentation HttpMessageHandlerBuilder par défaut) attribue si n’est pas configurée d’une manière quelconque.
Remarque
Le gestionnaire principal « factory-default » est un détail d’implémentation et susceptible de changer.
❌ ÉVITEZ de compter sur une implémentation spécifique utilisée comme réglage par défaut d'usine (par exemple, HttpClientHandler).
Il existe des cas dans lesquels vous devez connaître le type spécifique d’un gestionnaire principal, en particulier si vous travaillez sur une bibliothèque de classes. Tout en préservant la configuration de l'utilisateur final, vous pouvez mettre à jour, par exemple, les propriétés spécifiques de HttpClientHandler, telles que ClientCertificates, UseCookieset UseProxy. Il peut être tentant de caster le gestionnaire principal en HttpClientHandler, ce qui a fonctionné lorsque HttpClientHandler était utilisé comme gestionnaire principal « par défaut d'usine ». Toutefois, comme n’importe quel code qui dépend des détails d’implémentation, une telle solution de contournement est fragile vouée à l’échec.
Au lieu de vous appuyer sur le gestionnaire principal « factory-default », vous pouvez utiliser ConfigureHttpClientDefaults pour configurer une instance de gestionnaire principal par défaut « au niveau de l’application » :
// 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) { ... }
});
Vous pouvez également vérifier le type de gestionnaire principal et configurer les spécificités telles que les certificats clients uniquement dans les types de prise en charge connus (probablement, HttpClientHandler et SocketsHttpHandler) :
// --- "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) { ... }
});
Voir aussi
-
Problèmes courants d’utilisation de
IHttpClientFactory - Injection de dépendances dans .NET
- Journalisation dans .NET
- Configuration dans .NET
- IHttpClientFactory
- IHttpMessageHandlerFactory
- HttpClient
- Effectuer des requêtes HTTP avec HttpClient
- Implémenter une nouvelle tentative HTTP avec backoff exponentiel