Effectuer des requêtes HTTP en utilisant IHttpClientFactory dans ASP.NET Core
Remarque
Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 9 de cet article.
Avertissement
Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la Stratégie de prise en charge de .NET et .NET Core. Pour la version actuelle, consultez la version .NET 8 de cet article.
Important
Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.
Pour la version actuelle, consultez la version .NET 9 de cet article.
Par Kirk Larkin, Steve Gordon, Glenn Condron et Ryan Nowak.
Une IHttpClientFactory peut être inscrite et utilisée pour configurer et créer des instances de HttpClient dans une application. IHttpClientFactory
offre les avantages suivants :
- Fournit un emplacement central pour le nommage et la configuration d’instance de
HttpClient
logiques. Par exemple, un client nommé github peut être inscrit et configuré pour accéder à GitHub. Un client par défaut peut être inscrit pour l’accès général. - Codifie le concept d’intergiciel (middleware) sortant via la délégation de gestionnaires dans
HttpClient
. Fournit des extensions pour les intergiciels basés sur Polly afin de tirer parti de la délégation de gestionnaires dansHttpClient
. - Gère les pools et la durée de vie des instances
HttpClientMessageHandler
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 vieHttpClient
. - Ajoute une expérience de journalisation configurable (via
ILogger
) pour toutes les requêtes envoyées via des clients créés par la fabrique.
L’exemple de code de cette version de rubrique utilise System.Text.Json pour désérialiser le contenu JSON renvoyé dans les réponses HTTP. Pour les exemples qui utilisent Json.NET
et ReadAsAsync<T>
, utilisez le sélecteur de version pour sélectionner une version 2.x de cette rubrique.
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
Inscrivez IHttpClientFactory
en appelant AddHttpClient
dans Program.cs
:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient();
Une IHttpClientFactory
peut être demandée à l’aide de l’injection de dépendances (DI). Le code suivant utilise IHttpClientFactory
pour créer une instance 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);
}
}
}
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
HttpClient
ont une configuration différente.
Spécifiez la configuration d’un HttpClient
nommé pendant son inscription dans 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");
});
Dans le code précédent, le client est configuré avec :
- L’adresse de base
https://api.github.com/
. - Deux en-têtes requis pour fonctionner avec l’API GitHub.
CreateClient
Chaque fois que CreateClient est appelé :
- Une nouvelle instance de
HttpClient
est créée. - L’action de configuration est appelée.
Pour créer un client nommé, passez son nom dans 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);
}
}
}
Dans le code précédent, la requête 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.
- Bénéficie de l’aide d’IntelliSense et du compilateur lors de l’utilisation des clients.
- Fournissent un emplacement unique pour configurer et interagir avec un
HttpClient
particulier. 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 :
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");
}
Dans le code précédent :
- La configuration est déplacée dans le client typé.
- L’instance
HttpClient
fournie est stockée en tant que champ privé.
Vous pouvez créer des méthodes spécifiques à l’API qui exposent des fonctionnalités de HttpClient
. Par exemple, la méthode encapsule le code GetAspNetCoreDocsBranches
pour récupérer des branches GitHub de documentation.
Le code suivant appelle AddHttpClient dans Program.cs
pour inscrire une classe cliente typée GitHubService
:
builder.Services.AddHttpClient<GitHubService>();
Le client typé est inscrit comme étant transitoire avec injection de dépendances. Dans le code précédent, AddHttpClient
inscrit GitHubService
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
GitHubService
en passant l’instance deHttpClient
à son constructeur.
Le client typé peut être injecté et utilisé directement :
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)
{
// ...
}
}
}
Vous pouvez également spécifier la configuration d’un client typé lors de l’inscription dans Program.cs
au lieu de le faire dans le constructeur du client typé :
builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// ...
});
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. Il convertit les API REST en interfaces dynamiques. Appelez AddRefitClient
pour générer une implémentation dynamique d’une interface, qui utilise HttpClient
pour effectuer les appels HTTP externes.
Une interface personnalisée représente l’API externe :
public interface IGitHubClient
{
[Get("/repos/dotnet/AspNetCore.Docs/branches")]
Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}
Appelez AddRefitClient
pour générer l’implémentation dynamique, puis appelez ConfigureHttpClient
pour configurer le HttpClient
sous-jacent :
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");
});
Utilisez la DI pour accéder à l’implémentation dynamique 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)
{
// ...
}
}
}
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 :
- POST
- PUT
- DELETE
- PATCH
Pour obtenir la liste complète des verbes HTTP pris en charge, consultez HttpMethod.
L’exemple suivant montre comment effectuer une requête HTTP POST :
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();
}
Dans le code précédent, la méthode CreateItemAsync
:
- Sérialise le paramètre
TodoItem
au format JSON à l’aide deSystem.Text.Json
. - 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 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();
}
Le code précédent est similaire à l’exemple POST La méthode SaveItemAsync
appelle PutAsync au lieu de PostAsync
.
L’exemple suivant montre une requête HTTP DELETE :
public async Task DeleteItemAsync(long itemId)
{
using var httpResponseMessage =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponseMessage.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 méthode DeleteAsync
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.
Middleware pour les requêtes sortantes
HttpClient
intègre le concept de délégation des gestionnaires qui peuvent être liés ensemble pour les requêtes HTTP sortantes. IHttpClientFactory
:
- Simplifie la définition des gestionnaires à appliquer pour chaque client nommé.
- Il prend en charge l’inscription et le chaînage de plusieurs gestionnaires pour créer un pipeline d’intergiciels pour les requêtes sortantes. Chacun de ces gestionnaires peut effectuer un travail avant et après la requête sortante. Ce modèle :
- Est similaire au pipeline d’intergiciels entrants dans ASP.NET Core.
- Fournit un mécanisme pour gérer les problèmes transversaux liés aux requêtes HTTP, par exemple :
- mise en cache
- gestion des erreurs
- sérialisation
- journalisation
Pour créer un gestionnaire de délégation :
- Dérivez de DelegatingHandler.
- Remplacez SendAsync. Exécutez du code avant de passer la requête au gestionnaire suivant dans le pipeline :
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"The API key header X-API-KEY is required.")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Le code précédent vérifie si l’en-tête X-API-KEY
se trouve dans la requête. Si X-API-KEY
est manquant, BadRequest est retourné.
Plusieurs gestionnaires peuvent être ajoutés à la configuration d’un HttpClient
avec Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler :
builder.Services.AddTransient<ValidateHeaderHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<ValidateHeaderHandler>();
Dans le code précédent, le ValidateHeaderHandler
est inscrit avec une injection de dépendances. Une fois inscrit, AddHttpMessageHandler peut être appelé en passant en entrée le type pour le gestionnaire.
Vous pouvez inscrire plusieurs gestionnaires dans l’ordre où ils doivent être exécutés. Chaque gestionnaire wrappe le gestionnaire suivant jusqu’à ce que le dernier HttpClientHandler
exécute la requête :
builder.Services.AddTransient<SampleHandler1>();
builder.Services.AddTransient<SampleHandler2>();
builder.Services.AddHttpClient("MultipleHttpMessageHandlers")
.AddHttpMessageHandler<SampleHandler1>()
.AddHttpMessageHandler<SampleHandler2>();
Dans le code précédent, SampleHandler1
s’exécute en premier, avant SampleHandler2
.
Utiliser l’intergiciel de requêtes sortantes
Lorsque IHttpClientFactory
crée un gestionnaire de délégation, il utilise l’injection de dépendances pour remplir les paramètres du constructeur du gestionnaire. IHttpClientFactory
crée une étendue de DI distincte pour chaque gestionnaire, ce qui peut entraîner un comportement surprenant lorsqu’un gestionnaire consomme un service délimité.
Par exemple, considérez l’interface suivante et son implémentation, qui représente une tâche en tant qu’opération avec un identificateur, OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Comme son nom l’indique, IOperationScoped
est inscrit avec la DI à l’aide d’une durée de vie étendue :
builder.Services.AddScoped<IOperationScoped, OperationScoped>();
Le gestionnaire de délégation suivant consomme et utilise IOperationScoped
pour définir l’en-tête X-OPERATION-ID
pour la requête sortante :
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);
}
}
Dans le Téléchargement de HttpRequestsSample
, accédez à /Operation
et actualisez la page. La valeur de l’étendue de la requête change pour chaque requête, mais la valeur d’étendue du gestionnaire change uniquement toutes les 5 secondes.
Les gestionnaires peuvent dépendre des services de n’importe quelle étendue. Les services dont dépendent les gestionnaires sont supprimés lorsque le gestionnaire est supprimé.
Utilisez l’une des approches suivantes pour partager l’état de chaque requête avec les gestionnaires de messages :
- Passez des données dans le gestionnaire en utilisant HttpRequestMessage.Options.
- Utilisez IHttpContextAccessor pour accéder à la requête en cours.
- Créez un objet de stockage AsyncLocal<T> personnalisé pour passer les données.
Utiliser les gestionnaires Polly
IHttpClientFactory
s’intègre à la bibliothèque tierce Polly. Polly est une bibliothèque complète de gestion des erreurs transitoires et de résilience pour .NET. Elle permet aux développeurs de formuler facilement et de façon thread-safe des stratégies, comme Retry (Nouvelle tentative), Circuit Breaker (Disjoncteur), Timeout (Délai d’attente), Bulkhead Isolation (Isolation par cloisonnement) et Fallback (Alternative de repli).
Des méthodes d’extension sont fournies pour permettre l’utilisation de stratégies Polly avec les instances configurées de HttpClient
. Les extensions Polly prennent en charge l’ajout de gestionnaires basés sur Polly aux clients. Polly nécessite le package NuGet Microsoft.Extensions.Http.Polly.
Gérer les erreurs temporaires
Les erreurs se produisent généralement lorsque des appels HTTP externes sont temporaires. AddTransientHttpErrorPolicy permet à une stratégie d’être définie pour gérer les erreurs temporaires. Les stratégies configurées avec AddTransientHttpErrorPolicy
gèrent les réponses suivantes :
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
fournit l’accès à un objet PolicyBuilder
configuré pour gérer les erreurs représentant une erreur temporaire possible :
builder.Services.AddHttpClient("PollyWaitAndRetry")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3, retryNumber => TimeSpan.FromMilliseconds(600)));
Dans le code précédent, une stratégie WaitAndRetryAsync
est définie. Les requêtes qui ont échoué sont retentées jusqu’à trois fois avec un délai de 600 ms entre les tentatives.
Sélectionner dynamiquement des stratégies
Des méthodes d’extension sont fournies pour ajouter des gestionnaires basés sur Polly, par exemple AddPolicyHandler. La surcharge de AddPolicyHandler
suivante inspecte la requête pour déterminer la stratégie à appliquer :
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);
Dans le code précédent, si la requête sortante est un HTTP GET, un délai d’attente de 10 secondes est appliqué. Pour toutes les autres méthodes HTTP, un délai d’attente de 30 secondes est utilisé.
Ajouter plusieurs gestionnaires Polly
Il est courant d’imbriquer les stratégies Polly :
builder.Services.AddHttpClient("PollyMultiple")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.RetryAsync(3))
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Dans l’exemple précédent :
- Deux gestionnaires sont ajoutés.
- Le premier gestionnaire utilise AddTransientHttpErrorPolicy pour ajouter une stratégie de nouvelle tentative. Les requêtes qui ont échoué sont retentées jusqu’à trois fois.
- Le deuxième appel à
AddTransientHttpErrorPolicy
ajoute une stratégie de disjoncteur. Les requêtes externes supplémentaires sont bloquées pendant 30 secondes si 5 tentatives successives échouent. Les stratégies de disjoncteur sont avec état. Tous les appels effectués via ce client partagent le même état du circuit.
Ajouter des stratégies à partir du Registre Polly
Une approche de la gestion des stratégies régulièrement utilisées consiste à les définir une seule fois et à les inscrire avec un PolicyRegistry
. Par exemple :
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");
Dans le code précédent :
- Deux stratégies,
Regular
etLong
, sont ajoutées au registre Polly. - AddPolicyHandlerFromRegistry configure des clients nommés individuels pour utiliser ces stratégies à partir du registre Polly.
Pour plus d’informations sur les intégrations IHttpClientFactory
et Polly, consultez le Wiki Polly.
HttpClient et gestion de la durée de vie
Une nouvelle instance HttpClient
est retournée à chaque fois que CreateClient
est appelé sur IHttpClientFactory
. Un HttpMessageHandler est créé par client nommé. La fabrique gère les durées de vie des instances HttpMessageHandler
.
IHttpClientFactory
regroupe dans un pool les instances de HttpMessageHandler
créées par la fabrique pour réduire la consommation des ressources. Une instance de HttpMessageHandler
peut être réutilisée à partir du pool quand vous créez une instance de HttpClient
si sa durée de vie n’a pas expiré.
Le regroupement de gestionnaires en pools est souhaitable, car chaque gestionnaire gère habituellement ses propres connexions HTTP sous-jacentes. La création de plus de gestionnaires que nécessaire peut entraîner 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 (Domain Name System).
La durée de vie par défaut d’un gestionnaire est de deux minutes. La valeur par défaut peut être remplacée pour chaque client nommé :
builder.Services.AddHttpClient("HandlerLifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Les instances HttpClient
peuvent généralement être traitées en tant qu’objets .NET ne nécessitant pas une suppression. La suppression annule les requêtes sortantes et garantit que l’instance HttpClient
donnée ne peut pas être utilisée après avoir appelé Dispose. IHttpClientFactory
effectue le suivi et libère les ressources utilisées par les instances HttpClient
.
Le fait de conserver une seule instance HttpClient
active pendant une longue durée est un modèle commun utilisé avant le lancement de IHttpClientFactory
. Ce modèle devient inutile après la migration vers IHttpClientFactory
.
Alternatives à IHttpClientFactory
L’utilisation de IHttpClientFactory
dans une application avec injection de dépendances évite :
- Les problèmes d’épuisement des ressources en regroupant les instances
HttpMessageHandler
. - Les problèmes de DNS obsolète en permutant les instances
HttpMessageHandler
à intervalles réguliers.
Il existe d’autres façons de résoudre les problèmes précédents à l’aide d’une instance SocketsHttpHandler de longue durée.
- Créez une instance de
SocketsHttpHandler
lorsque l’application démarre et utilisez-la pour la durée de vie de l’application. - Configurez PooledConnectionLifetime sur une valeur appropriée en fonction des temps d’actualisation DNS.
- Créez des instances
HttpClient
à l’aide denew HttpClient(handler, disposeHandler: false)
si nécessaire.
Les approches précédentes résolvent les problèmes de gestion des ressources que IHttpClientFactory
résout de la même manière.
- Le
SocketsHttpHandler
partage les connexions entre les instancesHttpClient
. Ce partage empêche l’épuisement des sockets. - Le
SocketsHttpHandler
permute les connexions en fonction dePooledConnectionLifetime
pour éviter les problèmes de DNS obsolète.
Journalisation
Les clients créés via IHttpClientFactory
enregistrent les messages de journalisation pour toutes les requêtes. Activez le niveau d’informations approprié dans la configuration de journalisation pour voir les messages de journalisation par défaut. Une journalisation supplémentaire, comme celle des en-têtes des requêtes, est incluse seulement au niveau de trace.
La catégorie de journal utilisée pour chaque client comprend le nom du client. Par exemple, un client nommé MyNamedClient journalise les messages avec la catégorie « System.Net.Http.HttpClient.MyNamedClient.LogicalHandler ». Les messages avec le suffixe LogicalHandler se produisent en dehors du pipeline du gestionnaire de requêtes. Lors d’une requête, les messages sont journalisés avant que d’autres gestionnaires du pipeline l’aient traitée. Lors d’une réponse, les messages sont journalisés une fois que tous les autres gestionnaires du pipeline ont reçu la réponse.
La journalisation se produit également à l’intérieur du pipeline du gestionnaire de requêtes. Dans l’exemple MyNamedClient, ces messages sont enregistrés avec la catégorie de journal « System.Net.Http.HttpClient.MyNamedClient.ClientHandler ». Pour la requête, cela se produit après que tous les autres gestionnaires ont été exécutés et immédiatement avant l’envoi de la requête. Lors de la réponse, cette journalisation inclut l’état de la réponse avant qu’elle repasse à travers le pipeline de gestionnaires.
L’activation de la journalisation à l’extérieur et à l’intérieur du pipeline permet l’inspection des changements apportés par les autres gestionnaires du pipeline. Cela peut comprendre des changements apportés aux en-têtes des requêtes ou au code d’état de la réponse.
L’ajout du nom du client dans la catégorie de journalisation permet de filtrer le journal pour des clients nommés spécifiques.
Configurer le HttpMessageHandler
Il peut être nécessaire de contrôler la configuration du HttpMessageHandler
interne utilisé par un client.
Un IHttpClientBuilder
est retourné quand vous ajoutez des clients nommés ou typés. La méthode d’extension ConfigurePrimaryHttpMessageHandler peut être utilisée pour définir un délégué. Le délégué est utilisé pour créer et configurer le HttpMessageHandler
principal utilisé par ce client :
builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
AllowAutoRedirect = true,
UseDefaultCredentials = true
});
Cookies
Le regroupement des instances HttpMessageHandler
engendre le partage d’objets CookieContainer
. Le partage d’objets CookieContainer
imprévus entraîne souvent un code incorrect. Pour les applications qui nécessitent des cookies, tenez compte de ce qui suit :
- Désactivation de la gestion de cookie automatique
- Éviter
IHttpClientFactory
Appelez ConfigurePrimaryHttpMessageHandler pour désactiver la gestion de cookie automatique :
builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});
Utiliser IHttpClientFactory dans une application console
Dans une application console, ajoutez les références de package suivantes au projet :
Dans l’exemple suivant :
- IHttpClientFactory et
GitHubService
sont inscrits dans le conteneur de service dans de l’hôte générique. GitHubService
est demandé à la DI, qui demande à son tour une instance deIHttpClientFactory
.GitHubService
utiliseIHttpClientFactory
pour créer une instance deHttpClient
, qu’elle utilise pour récupérer des branches GitHub de documentation.
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);
Intergiciel de propagation d’en-tête
La propagation d’en-tête est un intergiciel ASP.NET Core pour propager les en-têtes HTTP de la requête entrante vers les requêtes HttpClient
sortantes. Pour utiliser la propagation d’en-tête :
Installez le package Microsoft.AspNetCore.HeaderPropagation.
Configurez le pipeline de
HttpClient
et d’intergiciel dansProgram.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();
Effectuez des requêtes sortantes à l’aide de l’instance
HttpClient
configurée, qui inclut les en-têtes ajoutés.
Ressources supplémentaires
- Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
- Utilisez HttpClientFactory pour implémenter des requêtes HTTP résilientes
- Implémenter de nouvelles tentatives d’appel HTTP avec interruption exponentielle avec des stratégies Polly et HttpClientFactory
- Implémenter le modèle Disjoncteur
- Comment sérialiser et désérialiser JSON dans .NET
Par Kirk Larkin, Steve Gordon, Glenn Condron et Ryan Nowak.
Une IHttpClientFactory peut être inscrite et utilisée pour configurer et créer des instances de HttpClient dans une application. IHttpClientFactory
offre les avantages suivants :
- Fournit un emplacement central pour le nommage et la configuration d’instance de
HttpClient
logiques. Par exemple, un client nommé github peut être inscrit et configuré pour accéder à GitHub. Un client par défaut peut être inscrit pour l’accès général. - Codifie le concept d’intergiciel (middleware) sortant via la délégation de gestionnaires dans
HttpClient
. Fournit des extensions pour les intergiciels basés sur Polly afin de tirer parti de la délégation de gestionnaires dansHttpClient
. - Gère les pools et la durée de vie des instances
HttpClientMessageHandler
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 vieHttpClient
. - Ajoute une expérience de journalisation configurable (via
ILogger
) pour toutes les requêtes envoyées via des clients créés par la fabrique.
Affichez ou téléchargez un exemple de code (procédure de téléchargement).
L’exemple de code de cette version de rubrique utilise System.Text.Json pour désérialiser le contenu JSON renvoyé dans les réponses HTTP. Pour les exemples qui utilisent Json.NET
et ReadAsAsync<T>
, utilisez le sélecteur de version pour sélectionner une version 2.x de cette rubrique.
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
IHttpClientFactory
peut être inscrit en appelant 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.
Une IHttpClientFactory
peut être demandée à l’aide de l’injection de dépendances (DI). Le code suivant utilise IHttpClientFactory
pour créer une instance 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>();
}
}
}
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
HttpClient
ont une configuration différente.
La configuration d’un HttpClient
nommé peut être spécifiée lors de l’inscription dans 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");
});
Dans le code précédent, le client est configuré avec :
- L’adresse de base
https://api.github.com/
. - Deux en-têtes requis pour fonctionner avec l’API GitHub.
CreateClient
Chaque fois que CreateClient est appelé :
- Une nouvelle instance de
HttpClient
est créée. - L’action de configuration est appelée.
Pour créer un client nommé, passez son nom dans 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>();
}
}
}
Dans le code précédent, la requête 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.
- Bénéficie de l’aide d’IntelliSense et du compilateur lors de l’utilisation des clients.
- Fournissent un emplacement unique pour configurer et interagir avec un
HttpClient
particulier. 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 :
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");
}
}
Dans le code précédent :
- La configuration est déplacée dans le client typé.
- L’objet
HttpClient
est exposé en tant que propriété publique.
Vous pouvez créer des méthodes spécifiques à l’API qui exposent des fonctionnalités de HttpClient
. Par exemple, la méthode encapsule le code GetAspNetDocsIssues
pour récupérer des problèmes ouverts.
Le code suivant appelle AddHttpClient dans Startup.ConfigureServices
pour inscrire une classe cliente typée :
services.AddHttpClient<GitHubService>();
Le client typé est inscrit comme étant transitoire avec injection de dépendances. Dans le code précédent, AddHttpClient
inscrit GitHubService
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
GitHubService
en passant l’instance deHttpClient
à son constructeur.
Le client typé peut être injecté et utilisé directement :
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>();
}
}
}
Vous pouvez spécifier la configuration d’un client typé lors de l’inscription dans Startup.ConfigureServices
au lieu de le faire dans le constructeur du client typé :
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
peut être encapsulé dans un client typé. Au lieu de l’exposer en tant que propriété, définissez une méthode qui appelle l’instance HttpClient
en interne :
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);
}
}
Dans le code précédent, le HttpClient
est stocké dans un champ privé. L’accès à HttpClient
se fait par la méthode publique GetRepos
.
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. Il convertit les API REST en interfaces dynamiques. Une implémentation de l’interface est générée dynamiquement par le RestService
, avec HttpClient
pour faire les appels HTTP externes.
Une interface et une réponse sont définies pour représenter l’API externe et sa réponse :
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Vous pouvez ajouter un client typé en utilisant Refit pour générer l’implémentation :
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();
}
L’interface définie peut être utilisée quand c’est nécessaire, avec l’implémentation fournie par l’injection de dépendances et 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();
}
}
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 :
- POST
- PUT
- DELETE
- PATCH
Pour obtenir la liste complète des verbes HTTP pris en charge, consultez HttpMethod.
L’exemple suivant montre comment effectuer une requête HTTP POST :
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();
}
Dans le code précédent, la méthode CreateItemAsync
:
- Sérialise le paramètre
TodoItem
au 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 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();
}
Le code précédent est très similaire à l’exemple POST. La méthode SaveItemAsync
appelle PutAsync au lieu de PostAsync
.
L’exemple suivant montre une requête HTTP DELETE :
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
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 méthode DeleteAsync
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.
Middleware pour les requêtes sortantes
HttpClient
intègre le concept de délégation des gestionnaires qui peuvent être liés ensemble pour les requêtes HTTP sortantes. IHttpClientFactory
:
- Simplifie la définition des gestionnaires à appliquer pour chaque client nommé.
- Il prend en charge l’inscription et le chaînage de plusieurs gestionnaires pour créer un pipeline d’intergiciels pour les requêtes sortantes. Chacun de ces gestionnaires peut effectuer un travail avant et après la requête sortante. Ce modèle :
- Est similaire au pipeline d’intergiciels entrants dans ASP.NET Core.
- Fournit un mécanisme pour gérer les problèmes transversaux liés aux requêtes HTTP, par exemple :
- mise en cache
- gestion des erreurs
- sérialisation
- journalisation
Pour créer un gestionnaire de délégation :
- Dérivez de DelegatingHandler.
- Remplacez SendAsync. Exécutez du code avant de passer la requête au gestionnaire suivant dans le pipeline :
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Le code précédent vérifie si l’en-tête X-API-KEY
se trouve dans la requête. Si X-API-KEY
est manquant, BadRequest est retourné.
Plusieurs gestionnaires peuvent être ajoutés à la configuration d’un HttpClient
avec 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.
Dans le code précédent, le ValidateHeaderHandler
est inscrit avec une injection de dépendances. Une fois inscrit, AddHttpMessageHandler peut être appelé en passant en entrée le type pour le gestionnaire.
Vous pouvez inscrire plusieurs gestionnaires dans l’ordre où ils doivent être exécutés. Chaque gestionnaire wrappe le gestionnaire suivant jusqu’à ce que le dernier HttpClientHandler
exécute la requête :
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>();
Utiliser l’intergiciel de requêtes sortantes
Lorsque IHttpClientFactory
crée un gestionnaire de délégation, il utilise l’injection de dépendances pour remplir les paramètres du constructeur du gestionnaire. IHttpClientFactory
crée une étendue de DI distincte pour chaque gestionnaire, ce qui peut entraîner un comportement surprenant lorsqu’un gestionnaire consomme un service délimité.
Par exemple, considérez l’interface suivante et son implémentation, qui représente une tâche en tant qu’opération avec un identificateur, OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Comme son nom l’indique, IOperationScoped
est inscrit avec la DI à l’aide d’une durée de vie étendue :
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();
}
Le gestionnaire de délégation suivant consomme et utilise IOperationScoped
pour définir l’en-tête X-OPERATION-ID
pour la requête sortante :
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);
}
}
Dans le Téléchargement de HttpRequestsSample
], accédez à /Operation
et actualisez la page. La valeur de l’étendue de la requête change pour chaque requête, mais la valeur d’étendue du gestionnaire change uniquement toutes les 5 secondes.
Les gestionnaires peuvent dépendre des services de n’importe quelle étendue. Les services dont dépendent les gestionnaires sont supprimés lorsque le gestionnaire est supprimé.
Utilisez l’une des approches suivantes pour partager l’état de chaque requête avec les gestionnaires de messages :
- Passez des données dans le gestionnaire en utilisant HttpRequestMessage.Options.
- Utilisez IHttpContextAccessor pour accéder à la requête en cours.
- Créez un objet de stockage AsyncLocal<T> personnalisé pour passer les données.
Utiliser les gestionnaires Polly
IHttpClientFactory
s’intègre à la bibliothèque tierce Polly. Polly est une bibliothèque complète de gestion des erreurs transitoires et de résilience pour .NET. Elle permet aux développeurs de formuler facilement et de façon thread-safe des stratégies, comme Retry (Nouvelle tentative), Circuit Breaker (Disjoncteur), Timeout (Délai d’attente), Bulkhead Isolation (Isolation par cloisonnement) et Fallback (Alternative de repli).
Des méthodes d’extension sont fournies pour permettre l’utilisation de stratégies Polly avec les instances configurées de HttpClient
. Les extensions Polly prennent en charge l’ajout de gestionnaires basés sur Polly aux clients. Polly nécessite le package NuGet Microsoft.Extensions.Http.Polly.
Gérer les erreurs temporaires
Les erreurs se produisent généralement lorsque des appels HTTP externes sont temporaires. AddTransientHttpErrorPolicy permet à une stratégie d’être définie pour gérer les erreurs temporaires. Les stratégies configurées avec AddTransientHttpErrorPolicy
gèrent les réponses suivantes :
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
fournit l’accès à un objet PolicyBuilder
configuré pour gérer les erreurs représentant une erreur temporaire possible :
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
Dans le code précédent, une stratégie WaitAndRetryAsync
est définie. Les requêtes qui ont échoué sont retentées jusqu’à trois fois avec un délai de 600 ms entre les tentatives.
Sélectionner dynamiquement des stratégies
Des méthodes d’extension sont fournies pour ajouter des gestionnaires basés sur Polly, par exemple AddPolicyHandler. La surcharge de AddPolicyHandler
suivante inspecte la requête pour déterminer la stratégie à appliquer :
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);
Dans le code précédent, si la requête sortante est un HTTP GET, un délai d’attente de 10 secondes est appliqué. Pour toutes les autres méthodes HTTP, un délai d’attente de 30 secondes est utilisé.
Ajouter plusieurs gestionnaires Polly
Il est courant d’imbriquer les stratégies Polly :
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Dans l’exemple précédent :
- Deux gestionnaires sont ajoutés.
- Le premier gestionnaire utilise AddTransientHttpErrorPolicy pour ajouter une stratégie de nouvelle tentative. Les requêtes qui ont échoué sont retentées jusqu’à trois fois.
- Le deuxième appel à
AddTransientHttpErrorPolicy
ajoute une stratégie de disjoncteur. Les requêtes externes supplémentaires sont bloquées pendant 30 secondes si 5 tentatives successives échouent. Les stratégies de disjoncteur sont avec état. Tous les appels effectués via ce client partagent le même état du circuit.
Ajouter des stratégies à partir du Registre Polly
Une approche de la gestion des stratégies régulièrement utilisées consiste à les définir une seule fois et à les inscrire avec un PolicyRegistry
.
Dans le code suivant :
- Les stratégies « regular » et « long » sont ajoutées.
- AddPolicyHandlerFromRegistry ajoute les stratégies « regular » et « long » à partir du Registre.
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.
Pour plus d’informations sur les intégrations IHttpClientFactory
et Polly, consultez le Wiki Polly.
HttpClient et gestion de la durée de vie
Une nouvelle instance HttpClient
est retournée à chaque fois que CreateClient
est appelé sur IHttpClientFactory
. Un HttpMessageHandler est créé par client nommé. La fabrique gère les durées de vie des instances HttpMessageHandler
.
IHttpClientFactory
regroupe dans un pool les instances de HttpMessageHandler
créées par la fabrique pour réduire la consommation des ressources. Une instance de HttpMessageHandler
peut être réutilisée à partir du pool quand vous créez une instance de HttpClient
si sa durée de vie n’a pas expiré.
Le regroupement de gestionnaires en pools est souhaitable, car chaque gestionnaire gère habituellement ses propres connexions HTTP sous-jacentes. La création de plus de gestionnaires que nécessaire peut entraîner 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 (Domain Name System).
La durée de vie par défaut d’un gestionnaire est de deux minutes. La valeur par défaut peut être remplacée pour chaque client nommé :
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
Les instances HttpClient
peuvent généralement être traitées en tant qu’objets .NET ne nécessitant pas une suppression. La suppression annule les requêtes sortantes et garantit que l’instance HttpClient
donnée ne peut pas être utilisée après avoir appelé Dispose. IHttpClientFactory
effectue le suivi et libère les ressources utilisées par les instances HttpClient
.
Le fait de conserver une seule instance HttpClient
active pendant une longue durée est un modèle commun utilisé avant le lancement de IHttpClientFactory
. Ce modèle devient inutile après la migration vers IHttpClientFactory
.
Alternatives à IHttpClientFactory
L’utilisation de IHttpClientFactory
dans une application avec injection de dépendances évite :
- Les problèmes d’épuisement des ressources en regroupant les instances
HttpMessageHandler
. - Les problèmes de DNS obsolète en permutant les instances
HttpMessageHandler
à intervalles réguliers.
Il existe d’autres façons de résoudre les problèmes précédents à l’aide d’une instance SocketsHttpHandler de longue durée.
- Créez une instance de
SocketsHttpHandler
lorsque l’application démarre et utilisez-la pour la durée de vie de l’application. - Configurez PooledConnectionLifetime sur une valeur appropriée en fonction des temps d’actualisation DNS.
- Créez des instances
HttpClient
à l’aide denew HttpClient(handler, disposeHandler: false)
si nécessaire.
Les approches précédentes résolvent les problèmes de gestion des ressources que IHttpClientFactory
résout de la même manière.
- Le
SocketsHttpHandler
partage les connexions entre les instancesHttpClient
. Ce partage empêche l’épuisement des sockets. - Le
SocketsHttpHandler
permute les connexions en fonction dePooledConnectionLifetime
pour éviter les problèmes de DNS obsolète.
Cookies
Le regroupement des instances HttpMessageHandler
engendre le partage d’objets CookieContainer
. Le partage d’objets CookieContainer
imprévus entraîne souvent un code incorrect. Pour les applications qui nécessitent des cookies, tenez compte de ce qui suit :
- Désactivation de la gestion de cookie automatique
- Éviter
IHttpClientFactory
Appelez ConfigurePrimaryHttpMessageHandler pour désactiver la gestion de cookie automatique :
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Journalisation
Les clients créés via IHttpClientFactory
enregistrent les messages de journalisation pour toutes les requêtes. Activez le niveau d’informations approprié dans la configuration de journalisation pour voir les messages de journalisation par défaut. Une journalisation supplémentaire, comme celle des en-têtes des requêtes, est incluse seulement au niveau de trace.
La catégorie de journal utilisée pour chaque client comprend le nom du client. Par exemple, un client nommé MyNamedClient journalise les messages avec la catégorie « System.Net.Http.HttpClient.MyNamedClient.LogicalHandler ». Les messages avec le suffixe LogicalHandler se produisent en dehors du pipeline du gestionnaire de requêtes. Lors d’une requête, les messages sont journalisés avant que d’autres gestionnaires du pipeline l’aient traitée. Lors d’une réponse, les messages sont journalisés une fois que tous les autres gestionnaires du pipeline ont reçu la réponse.
La journalisation se produit également à l’intérieur du pipeline du gestionnaire de requêtes. Dans l’exemple MyNamedClient, ces messages sont enregistrés avec la catégorie de journal « System.Net.Http.HttpClient.MyNamedClient.ClientHandler ». Pour la requête, cela se produit après que tous les autres gestionnaires ont été exécutés et immédiatement avant l’envoi de la requête. Lors de la réponse, cette journalisation inclut l’état de la réponse avant qu’elle repasse à travers le pipeline de gestionnaires.
L’activation de la journalisation à l’extérieur et à l’intérieur du pipeline permet l’inspection des changements apportés par les autres gestionnaires du pipeline. Cela peut comprendre des changements apportés aux en-têtes des requêtes ou au code d’état de la réponse.
L’ajout du nom du client dans la catégorie de journalisation permet de filtrer le journal pour des clients nommés spécifiques.
Configurer le HttpMessageHandler
Il peut être nécessaire de contrôler la configuration du HttpMessageHandler
interne utilisé par un client.
Un IHttpClientBuilder
est retourné quand vous ajoutez des clients nommés ou typés. La méthode d’extension ConfigurePrimaryHttpMessageHandler peut être utilisée pour définir un délégué. Le délégué est utilisé pour créer et configurer le HttpMessageHandler
principal utilisé par ce client :
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
Utiliser IHttpClientFactory dans une application console
Dans une application console, ajoutez les références de package suivantes au projet :
Dans l’exemple suivant :
- IHttpClientFactory est inscrit dans le conteneur de service dans de l’hôte générique.
MyService
crée une instance de fabrique cliente à partir du service, qui est utilisée pour créer unHttpClient
.HttpClient
est utilisé pour récupérer une page web.Main
crée une étendue pour exécuter la méthodeGetPage
du service et écrire les 500 premiers caractères du contenu de la page web dans la console.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
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}";
}
}
}
}
Intergiciel de propagation d’en-tête
La propagation d’en-tête est un intergiciel ASP.NET Core pour propager les en-têtes HTTP de la requête entrante vers les requêtes client HTTP sortantes. Pour utiliser la propagation d’en-tête :
Référencez le package Microsoft.AspNetCore.HeaderPropagation.
Configurez l’intergiciel et
HttpClient
dansStartup
: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(); }); }
Le client inclut les en-têtes configurés sur les requêtes sortantes :
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Ressources supplémentaires
Par Kirk Larkin, Steve Gordon, Glenn Condron et Ryan Nowak.
Une IHttpClientFactory peut être inscrite et utilisée pour configurer et créer des instances de HttpClient dans une application. IHttpClientFactory
offre les avantages suivants :
- Fournit un emplacement central pour le nommage et la configuration d’instance de
HttpClient
logiques. Par exemple, un client nommé github peut être inscrit et configuré pour accéder à GitHub. Un client par défaut peut être inscrit pour l’accès général. - Codifie le concept d’intergiciel (middleware) sortant via la délégation de gestionnaires dans
HttpClient
. Fournit des extensions pour les intergiciels basés sur Polly afin de tirer parti de la délégation de gestionnaires dansHttpClient
. - Gère les pools et la durée de vie des instances
HttpClientMessageHandler
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 vieHttpClient
. - Ajoute une expérience de journalisation configurable (via
ILogger
) pour toutes les requêtes envoyées via des clients créés par la fabrique.
Affichez ou téléchargez un exemple de code (procédure de téléchargement).
L’exemple de code de cette version de rubrique utilise System.Text.Json pour désérialiser le contenu JSON renvoyé dans les réponses HTTP. Pour les exemples qui utilisent Json.NET
et ReadAsAsync<T>
, utilisez le sélecteur de version pour sélectionner une version 2.x de cette rubrique.
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
IHttpClientFactory
peut être inscrit en appelant 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.
Une IHttpClientFactory
peut être demandée à l’aide de l’injection de dépendances (DI). Le code suivant utilise IHttpClientFactory
pour créer une instance 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>();
}
}
}
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
HttpClient
ont une configuration différente.
La configuration d’un HttpClient
nommé peut être spécifiée lors de l’inscription dans 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");
});
Dans le code précédent, le client est configuré avec :
- L’adresse de base
https://api.github.com/
. - Deux en-têtes requis pour fonctionner avec l’API GitHub.
CreateClient
Chaque fois que CreateClient est appelé :
- Une nouvelle instance de
HttpClient
est créée. - L’action de configuration est appelée.
Pour créer un client nommé, passez son nom dans 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>();
}
}
}
Dans le code précédent, la requête 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.
- Bénéficie de l’aide d’IntelliSense et du compilateur lors de l’utilisation des clients.
- Fournissent un emplacement unique pour configurer et interagir avec un
HttpClient
particulier. 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 :
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 vous souhaitez voir les commentaires de code traduits dans une langue autre que l’anglais, dites-le nous dans cette discussion GitHub.
Dans le code précédent :
- La configuration est déplacée dans le client typé.
- L’objet
HttpClient
est exposé en tant que propriété publique.
Vous pouvez créer des méthodes spécifiques à l’API qui exposent des fonctionnalités de HttpClient
. Par exemple, la méthode encapsule le code GetAspNetDocsIssues
pour récupérer des problèmes ouverts.
Le code suivant appelle AddHttpClient dans Startup.ConfigureServices
pour inscrire une classe cliente typée :
services.AddHttpClient<GitHubService>();
Le client typé est inscrit comme étant transitoire avec injection de dépendances. Dans le code précédent, AddHttpClient
inscrit GitHubService
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
GitHubService
en passant l’instance deHttpClient
à son constructeur.
Le client typé peut être injecté et utilisé directement :
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>();
}
}
}
Vous pouvez spécifier la configuration d’un client typé lors de l’inscription dans Startup.ConfigureServices
au lieu de le faire dans le constructeur du client typé :
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
peut être encapsulé dans un client typé. Au lieu de l’exposer en tant que propriété, définissez une méthode qui appelle l’instance HttpClient
en interne :
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);
}
}
Dans le code précédent, le HttpClient
est stocké dans un champ privé. L’accès à HttpClient
se fait par la méthode publique GetRepos
.
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. Il convertit les API REST en interfaces dynamiques. Une implémentation de l’interface est générée dynamiquement par le RestService
, avec HttpClient
pour faire les appels HTTP externes.
Une interface et une réponse sont définies pour représenter l’API externe et sa réponse :
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Vous pouvez ajouter un client typé en utilisant Refit pour générer l’implémentation :
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();
}
L’interface définie peut être utilisée quand c’est nécessaire, avec l’implémentation fournie par l’injection de dépendances et 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();
}
}
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 :
- POST
- PUT
- DELETE
- PATCH
Pour obtenir la liste complète des verbes HTTP pris en charge, consultez HttpMethod.
L’exemple suivant montre comment effectuer une requête HTTP POST :
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();
}
Dans le code précédent, la méthode CreateItemAsync
:
- Sérialise le paramètre
TodoItem
au 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 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();
}
Le code précédent est très similaire à l’exemple POST. La méthode SaveItemAsync
appelle PutAsync au lieu de PostAsync
.
L’exemple suivant montre une requête HTTP DELETE :
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
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 méthode DeleteAsync
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.
Middleware pour les requêtes sortantes
HttpClient
intègre le concept de délégation des gestionnaires qui peuvent être liés ensemble pour les requêtes HTTP sortantes. IHttpClientFactory
:
- Simplifie la définition des gestionnaires à appliquer pour chaque client nommé.
- Il prend en charge l’inscription et le chaînage de plusieurs gestionnaires pour créer un pipeline d’intergiciels pour les requêtes sortantes. Chacun de ces gestionnaires peut effectuer un travail avant et après la requête sortante. Ce modèle :
- Est similaire au pipeline d’intergiciels entrants dans ASP.NET Core.
- Fournit un mécanisme pour gérer les problèmes transversaux liés aux requêtes HTTP, par exemple :
- mise en cache
- gestion des erreurs
- sérialisation
- journalisation
Pour créer un gestionnaire de délégation :
- Dérivez de DelegatingHandler.
- Remplacez SendAsync. Exécutez du code avant de passer la requête au gestionnaire suivant dans le pipeline :
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Le code précédent vérifie si l’en-tête X-API-KEY
se trouve dans la requête. Si X-API-KEY
est manquant, BadRequest est retourné.
Plusieurs gestionnaires peuvent être ajoutés à la configuration d’un HttpClient
avec 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.
Dans le code précédent, le ValidateHeaderHandler
est inscrit avec une injection de dépendances. Une fois inscrit, AddHttpMessageHandler peut être appelé en passant en entrée le type pour le gestionnaire.
Vous pouvez inscrire plusieurs gestionnaires dans l’ordre où ils doivent être exécutés. Chaque gestionnaire wrappe le gestionnaire suivant jusqu’à ce que le dernier HttpClientHandler
exécute la requête :
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>();
Utiliser l’intergiciel de requêtes sortantes
Lorsque IHttpClientFactory
crée un gestionnaire de délégation, il utilise l’injection de dépendances pour remplir les paramètres du constructeur du gestionnaire. IHttpClientFactory
crée une étendue de DI distincte pour chaque gestionnaire, ce qui peut entraîner un comportement surprenant lorsqu’un gestionnaire consomme un service délimité.
Par exemple, considérez l’interface suivante et son implémentation, qui représente une tâche en tant qu’opération avec un identificateur, OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Comme son nom l’indique, IOperationScoped
est inscrit avec la DI à l’aide d’une durée de vie étendue :
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();
}
Le gestionnaire de délégation suivant consomme et utilise IOperationScoped
pour définir l’en-tête X-OPERATION-ID
pour la requête sortante :
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);
}
}
Dans le Téléchargement de HttpRequestsSample
], accédez à /Operation
et actualisez la page. La valeur de l’étendue de la requête change pour chaque requête, mais la valeur d’étendue du gestionnaire change uniquement toutes les 5 secondes.
Les gestionnaires peuvent dépendre des services de n’importe quelle étendue. Les services dont dépendent les gestionnaires sont supprimés lorsque le gestionnaire est supprimé.
Utilisez l’une des approches suivantes pour partager l’état de chaque requête avec les gestionnaires de messages :
- Passez des données dans le gestionnaire en utilisant HttpRequestMessage.Properties.
- Utilisez IHttpContextAccessor pour accéder à la requête en cours.
- Créez un objet de stockage AsyncLocal<T> personnalisé pour passer les données.
Utiliser les gestionnaires Polly
IHttpClientFactory
s’intègre à la bibliothèque tierce Polly. Polly est une bibliothèque complète de gestion des erreurs transitoires et de résilience pour .NET. Elle permet aux développeurs de formuler facilement et de façon thread-safe des stratégies, comme Retry (Nouvelle tentative), Circuit Breaker (Disjoncteur), Timeout (Délai d’attente), Bulkhead Isolation (Isolation par cloisonnement) et Fallback (Alternative de repli).
Des méthodes d’extension sont fournies pour permettre l’utilisation de stratégies Polly avec les instances configurées de HttpClient
. Les extensions Polly prennent en charge l’ajout de gestionnaires basés sur Polly aux clients. Polly nécessite le package NuGet Microsoft.Extensions.Http.Polly.
Gérer les erreurs temporaires
Les erreurs se produisent généralement lorsque des appels HTTP externes sont temporaires. AddTransientHttpErrorPolicy permet à une stratégie d’être définie pour gérer les erreurs temporaires. Les stratégies configurées avec AddTransientHttpErrorPolicy
gèrent les réponses suivantes :
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
fournit l’accès à un objet PolicyBuilder
configuré pour gérer les erreurs représentant une erreur temporaire possible :
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
Dans le code précédent, une stratégie WaitAndRetryAsync
est définie. Les requêtes qui ont échoué sont retentées jusqu’à trois fois avec un délai de 600 ms entre les tentatives.
Sélectionner dynamiquement des stratégies
Des méthodes d’extension sont fournies pour ajouter des gestionnaires basés sur Polly, par exemple AddPolicyHandler. La surcharge de AddPolicyHandler
suivante inspecte la requête pour déterminer la stratégie à appliquer :
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);
Dans le code précédent, si la requête sortante est un HTTP GET, un délai d’attente de 10 secondes est appliqué. Pour toutes les autres méthodes HTTP, un délai d’attente de 30 secondes est utilisé.
Ajouter plusieurs gestionnaires Polly
Il est courant d’imbriquer les stratégies Polly :
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Dans l’exemple précédent :
- Deux gestionnaires sont ajoutés.
- Le premier gestionnaire utilise AddTransientHttpErrorPolicy pour ajouter une stratégie de nouvelle tentative. Les requêtes qui ont échoué sont retentées jusqu’à trois fois.
- Le deuxième appel à
AddTransientHttpErrorPolicy
ajoute une stratégie de disjoncteur. Les requêtes externes supplémentaires sont bloquées pendant 30 secondes si 5 tentatives successives échouent. Les stratégies de disjoncteur sont avec état. Tous les appels effectués via ce client partagent le même état du circuit.
Ajouter des stratégies à partir du Registre Polly
Une approche de la gestion des stratégies régulièrement utilisées consiste à les définir une seule fois et à les inscrire avec un PolicyRegistry
.
Dans le code suivant :
- Les stratégies « regular » et « long » sont ajoutées.
- AddPolicyHandlerFromRegistry ajoute les stratégies « regular » et « long » à partir du Registre.
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.
Pour plus d’informations sur les intégrations IHttpClientFactory
et Polly, consultez le Wiki Polly.
HttpClient et gestion de la durée de vie
Une nouvelle instance HttpClient
est retournée à chaque fois que CreateClient
est appelé sur IHttpClientFactory
. Un HttpMessageHandler est créé par client nommé. La fabrique gère les durées de vie des instances HttpMessageHandler
.
IHttpClientFactory
regroupe dans un pool les instances de HttpMessageHandler
créées par la fabrique pour réduire la consommation des ressources. Une instance de HttpMessageHandler
peut être réutilisée à partir du pool quand vous créez une instance de HttpClient
si sa durée de vie n’a pas expiré.
Le regroupement de gestionnaires en pools est souhaitable, car chaque gestionnaire gère habituellement ses propres connexions HTTP sous-jacentes. La création de plus de gestionnaires que nécessaire peut entraîner 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 (Domain Name System).
La durée de vie par défaut d’un gestionnaire est de deux minutes. La valeur par défaut peut être remplacée pour chaque client nommé :
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
Les instances HttpClient
peuvent généralement être traitées en tant qu’objets .NET ne nécessitant pas une suppression. La suppression annule les requêtes sortantes et garantit que l’instance HttpClient
donnée ne peut pas être utilisée après avoir appelé Dispose. IHttpClientFactory
effectue le suivi et libère les ressources utilisées par les instances HttpClient
.
Le fait de conserver une seule instance HttpClient
active pendant une longue durée est un modèle commun utilisé avant le lancement de IHttpClientFactory
. Ce modèle devient inutile après la migration vers IHttpClientFactory
.
Alternatives à IHttpClientFactory
L’utilisation de IHttpClientFactory
dans une application avec injection de dépendances évite :
- Les problèmes d’épuisement des ressources en regroupant les instances
HttpMessageHandler
. - Les problèmes de DNS obsolète en permutant les instances
HttpMessageHandler
à intervalles réguliers.
Il existe d’autres façons de résoudre les problèmes précédents à l’aide d’une instance SocketsHttpHandler de longue durée.
- Créez une instance de
SocketsHttpHandler
lorsque l’application démarre et utilisez-la pour la durée de vie de l’application. - Configurez PooledConnectionLifetime sur une valeur appropriée en fonction des temps d’actualisation DNS.
- Créez des instances
HttpClient
à l’aide denew HttpClient(handler, disposeHandler: false)
si nécessaire.
Les approches précédentes résolvent les problèmes de gestion des ressources que IHttpClientFactory
résout de la même manière.
- Le
SocketsHttpHandler
partage les connexions entre les instancesHttpClient
. Ce partage empêche l’épuisement des sockets. - Le
SocketsHttpHandler
permute les connexions en fonction dePooledConnectionLifetime
pour éviter les problèmes de DNS obsolète.
Cookies
Le regroupement des instances HttpMessageHandler
engendre le partage d’objets CookieContainer
. Le partage d’objets CookieContainer
imprévus entraîne souvent un code incorrect. Pour les applications qui nécessitent des cookies, tenez compte de ce qui suit :
- Désactivation de la gestion de cookie automatique
- Éviter
IHttpClientFactory
Appelez ConfigurePrimaryHttpMessageHandler pour désactiver la gestion de cookie automatique :
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Journalisation
Les clients créés via IHttpClientFactory
enregistrent les messages de journalisation pour toutes les requêtes. Activez le niveau d’informations approprié dans la configuration de journalisation pour voir les messages de journalisation par défaut. Une journalisation supplémentaire, comme celle des en-têtes des requêtes, est incluse seulement au niveau de trace.
La catégorie de journal utilisée pour chaque client comprend le nom du client. Par exemple, un client nommé MyNamedClient journalise les messages avec la catégorie « System.Net.Http.HttpClient.MyNamedClient.LogicalHandler ». Les messages avec le suffixe LogicalHandler se produisent en dehors du pipeline du gestionnaire de requêtes. Lors d’une requête, les messages sont journalisés avant que d’autres gestionnaires du pipeline l’aient traitée. Lors d’une réponse, les messages sont journalisés une fois que tous les autres gestionnaires du pipeline ont reçu la réponse.
La journalisation se produit également à l’intérieur du pipeline du gestionnaire de requêtes. Dans l’exemple MyNamedClient, ces messages sont enregistrés avec la catégorie de journal « System.Net.Http.HttpClient.MyNamedClient.ClientHandler ». Pour la requête, cela se produit après que tous les autres gestionnaires ont été exécutés et immédiatement avant l’envoi de la requête. Lors de la réponse, cette journalisation inclut l’état de la réponse avant qu’elle repasse à travers le pipeline de gestionnaires.
L’activation de la journalisation à l’extérieur et à l’intérieur du pipeline permet l’inspection des changements apportés par les autres gestionnaires du pipeline. Cela peut comprendre des changements apportés aux en-têtes des requêtes ou au code d’état de la réponse.
L’ajout du nom du client dans la catégorie de journalisation permet de filtrer le journal pour des clients nommés spécifiques.
Configurer le HttpMessageHandler
Il peut être nécessaire de contrôler la configuration du HttpMessageHandler
interne utilisé par un client.
Un IHttpClientBuilder
est retourné quand vous ajoutez des clients nommés ou typés. La méthode d’extension ConfigurePrimaryHttpMessageHandler peut être utilisée pour définir un délégué. Le délégué est utilisé pour créer et configurer le HttpMessageHandler
principal utilisé par ce client :
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
Utiliser IHttpClientFactory dans une application console
Dans une application console, ajoutez les références de package suivantes au projet :
Dans l’exemple suivant :
- IHttpClientFactory est inscrit dans le conteneur de service dans de l’hôte générique.
MyService
crée une instance de fabrique cliente à partir du service, qui est utilisée pour créer unHttpClient
.HttpClient
est utilisé pour récupérer une page web.Main
crée une étendue pour exécuter la méthodeGetPage
du service et écrire les 500 premiers caractères du contenu de la page web dans la console.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
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}";
}
}
}
}
Intergiciel de propagation d’en-tête
La propagation d’en-tête est un intergiciel ASP.NET Core pour propager les en-têtes HTTP de la requête entrante vers les requêtes client HTTP sortantes. Pour utiliser la propagation d’en-tête :
Référencez le package Microsoft.AspNetCore.HeaderPropagation.
Configurez l’intergiciel et
HttpClient
dansStartup
: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(); }); }
Le client inclut les en-têtes configurés sur les requêtes sortantes :
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Ressources supplémentaires
By Glenn Condron, Ryan Nowak et Steve Gordon
Une IHttpClientFactory peut être inscrite et utilisée pour configurer et créer des instances de HttpClient dans une application. Elle offre les avantages suivants :
- Fournit un emplacement central pour le nommage et la configuration d’instance de
HttpClient
logiques. Par exemple, un client github peut être inscrit et configuré pour accéder à GitHub. Un client par défaut peut être inscrit à d’autres fins. - Codifie le concept de middleware (intergiciel) sortant via la délégation de gestionnaires dans
HttpClient
et fournit des extensions permettant au middleware Polly d’en tirer parti. - Gère le regroupement et la durée de vie des instances de
HttpClientMessageHandler
sous-jacentes pour éviter les problèmes DNS courants qui se produisent lors de la gestion manuelle des durées de vie deHttpClient
. - Ajoute une expérience de journalisation configurable (via
ILogger
) pour toutes les requêtes envoyées via des clients créés par la fabrique.
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Prérequis
Les projets ciblant .NET Framework nécessitent l’installation du package NuGet Microsoft.Extensions.Http. Les projets qui ciblent .NET Core et référencent le métapackage Microsoft.AspNetCore.App incluent déjà le package Microsoft.Extensions.Http
.
Modèles de consommation
Vous pouvez utiliser IHttpClientFactory
dans une application de plusieurs façons :
Aucune d’entre elles n’est meilleure qu’une autre. La meilleure approche dépend des contraintes de l’application.
Utilisation de base
Vous pouvez inscrire la IHttpClientFactory
en appelant la méthode d’extension AddHttpClient
sur la IServiceCollection
, à l’intérieur la méthode Startup.ConfigureServices
.
services.AddHttpClient();
Une fois inscrit, le code peut accepter un IHttpClientFactory
partout où des services peuvent être injectés avec une injection de dépendance. La IHttpClientFactory
peut être utilisée pour créer une instance 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>();
}
}
}
L’utilisation de IHttpClientFactory
de cette façon est un excellent moyen de refactoriser une application existante. Elle n’a aucun impact sur la façon dont HttpClient
est utilisé. Dans les endroits où les instances de HttpClient
sont actuellement créées, remplacez ces occurrences par un appel à CreateClient.
Clients nommés
Si une application nécessite plusieurs utilisations distinctes de HttpClient
, chacune avec une configuration différente, une option consiste à utiliser des clients nommés. La configuration d’un HttpClient
nommé peut être spécifiée lors de l’inscription dans 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");
});
Dans le code précédent, AddHttpClient
est appelé en fournissant le nom github. Une configuration par défaut est appliquée à ce client : l’adresse de base et deux en-têtes nécessaires pour utiliser l’API GitHub.
Chaque fois que CreateClient
est appelée, une nouvelle instance de HttpClient
est créée et l’action de configuration est appelée.
Pour utiliser un client nommé, un paramètre de chaîne peut être passé à CreateClient
. Spécifiez le nom du client à créer :
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>();
}
}
}
Dans le code précédent, la requête n’a pas besoin de spécifier un nom d’hôte. Elle 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.
- Bénéficie de l’aide d’IntelliSense et du compilateur lors de l’utilisation des clients.
- Fournissent un emplacement unique pour configurer et interagir avec un
HttpClient
particulier. Par exemple, un même client typé peut être utilisé pour un point de terminaison de backend et pour encapsuler la logique relative à ce point de terminaison. - Fonctionnent avec l’injection de dépendances et peuvent être injectés là où c’est nécessaire dans votre application.
Un client typé accepte un paramètre HttpClient
dans son constructeur :
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;
}
}
Dans le code précédent, la configuration est déplacée dans le client typé. L’objet HttpClient
est exposé en tant que propriété publique. Il est possible de définir des méthodes d’API spécifiques qui exposent les fonctionnalités de HttpClient
. La méthode GetAspNetDocsIssues
encapsule le code nécessaire pour interroger et analyser les problèmes ouverts les plus récents d’un dépôt GitHub.
Pour inscrire un client typé, la méthode d’extension AddHttpClient générique peut être utilisée dans Startup.ConfigureServices
, en spécifiant la classe du client typé :
services.AddHttpClient<GitHubService>();
Le client typé est inscrit comme étant transitoire avec injection de dépendances. Le client typé peut être injecté et utilisé directement :
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 vous préférez, vous pouvez spécifier la configuration d’un client typé lors de l’inscription dans Startup.ConfigureServices
au lieu de le faire dans le constructeur du client typé :
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");
});
Il est possible d’encapsuler entièrement le HttpClient
dans un client typé. Au lieu de l’exposer en tant que propriété, vous pouvez fournir des méthodes publiques qui appellent l’instance de HttpClient
en interne.
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;
}
}
Dans le code précédent, le HttpClient
est stocké en tant que champ privé. Tous les accès nécessaires pour effectuer des appels externes passent par la méthode GetRepos
.
Clients générés
IHttpClientFactory
peut être utilisé en combinaison avec d’autres bibliothèques tierces, comme Refit. Refit est une bibliothèque REST pour .NET. Il convertit les API REST en interfaces dynamiques. Une implémentation de l’interface est générée dynamiquement par le RestService
, avec HttpClient
pour faire les appels HTTP externes.
Une interface et une réponse sont définies pour représenter l’API externe et sa réponse :
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Vous pouvez ajouter un client typé en utilisant Refit pour générer l’implémentation :
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();
}
L’interface définie peut être utilisée quand c’est nécessaire, avec l’implémentation fournie par l’injection de dépendances et 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 pour les requêtes sortantes
HttpClient
intègre déjà le concept de délégation des gestionnaires qui peuvent être liés ensemble pour les requêtes HTTP sortantes. Le IHttpClientFactory
permet de définir facilement les gestionnaires à appliquer pour chaque client nommé. Il prend en charge l’inscription et le chaînage de plusieurs gestionnaires pour créer un pipeline de middlewares pour les requêtes sortantes. Chacun de ces gestionnaires peut effectuer un travail avant et après la requête sortante. Ce modèle est similaire au pipeline de middlewares entrants dans ASP.NET Core. Le modèle fournit un mécanisme permettant de gérer les problèmes transversaux liés aux des requêtes HTTP, notamment la mise en cache, la gestion des erreurs, la sérialisation et la journalisation.
Pour créer un gestionnaire, définissez une classe dérivant de DelegatingHandler. Remplacez la méthode SendAsync
de façon à exécuter du code avant de passer la requête au gestionnaire suivant dans le pipeline :
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Le code précédent définit un gestionnaire de base. Il vérifie si un en-tête X-API-KEY
a été inclus dans la requête. Si l’en-tête est manquant, il peut éviter l’appel HTTP et retourner une réponse appropriée.
Lors de l’inscription, un ou plusieurs gestionnaires peuvent être ajoutés à la configuration pour un HttpClient
. Cette tâche est accomplie via des méthodes d’extension sur le 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>();
Dans le code précédent, le ValidateHeaderHandler
est inscrit avec une injection de dépendances. Le gestionnaire doit être inscrit dans l’injection de dépendances en tant que service temporaire, sans étendue. Si le gestionnaire est inscrit en tant que service étendu et que des services dont dépend le gestionnaire peuvent être supprimés :
- Les services du gestionnaire peuvent être supprimés avant que le gestionnaire ne soit hors de portée.
- Les services du gestionnaire supprimés entraînent un échec du gestionnaire.
Une fois inscrit, AddHttpMessageHandler peut être appelé en passant en entrée le type du gestionnaire.
Vous pouvez inscrire plusieurs gestionnaires dans l’ordre où ils doivent être exécutés. Chaque gestionnaire wrappe le gestionnaire suivant jusqu’à ce que le dernier HttpClientHandler
exécute la requête :
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>();
Utilisez l’une des approches suivantes pour partager l’état de chaque requête avec les gestionnaires de messages :
- Passez des données dans le gestionnaire en utilisant
HttpRequestMessage.Properties
. - Utilisez
IHttpContextAccessor
pour accéder à la requête en cours. - Créez un objet de stockage
AsyncLocal
personnalisé pour passer les données.
Utiliser les gestionnaires Polly
IHttpClientFactory
s’intègre à une bibliothèque tierce très utilisée, appelée Polly. Polly est une bibliothèque complète de gestion des erreurs transitoires et de résilience pour .NET. Elle permet aux développeurs de formuler facilement et de façon thread-safe des stratégies, comme Retry (Nouvelle tentative), Circuit Breaker (Disjoncteur), Timeout (Délai d’attente), Bulkhead Isolation (Isolation par cloisonnement) et Fallback (Alternative de repli).
Des méthodes d’extension sont fournies pour permettre l’utilisation de stratégies Polly avec les instances configurées de HttpClient
. Les extensions Polly :
- Prennent en charge l’ajout de gestionnaires Polly à des clients.
- Peuvent être utilisées après l’installation du package NuGet Microsoft.Extensions.Http.Polly. Ce package n’est pas inclus dans le framework partagé ASP.NET Core.
Gérer les erreurs temporaires
Les erreurs courantes se produisent lorsque des appels HTTP externes sont temporaires. Une méthode d’extension pratique nommée AddTransientHttpErrorPolicy
est incluse : elle permet de définir une stratégie pour gérer les erreurs temporaires. Les stratégies configurées avec cette méthode d’extension gèrent HttpRequestException
, les réponses HTTP 5xx et les réponses HTTP 408.
L’extension AddTransientHttpErrorPolicy
peut être utilisée dans Startup.ConfigureServices
. L’extension fournit l’accès à un objet PolicyBuilder
configuré pour gérer les erreurs représentant une erreur temporaire possible :
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
Dans le code précédent, une stratégie WaitAndRetryAsync
est définie. Les requêtes qui ont échoué sont retentées jusqu’à trois fois avec un délai de 600 ms entre les tentatives.
Sélectionner dynamiquement des stratégies
Il existe d’autres méthodes d’extension que vous pouvez utiliser pour ajouter des gestionnaires Polly. Une de ces extensions est AddPolicyHandler
, qui a plusieurs surcharges. Une de ces surcharges permet l’inspection de la requête lors de la définition de la stratégie à appliquer :
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);
Dans le code précédent, si la requête sortante est un HTTP GET, un délai d’attente de 10 secondes est appliqué. Pour toutes les autres méthodes HTTP, un délai d’attente de 30 secondes est utilisé.
Ajouter plusieurs gestionnaires Polly
Il est courant d’imbriquer des stratégies Polly pour fournir des fonctionnalités améliorées :
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Dans l’exemple précédent, deux gestionnaires sont ajoutés. Le premier utilise l’extension AddTransientHttpErrorPolicy
pour ajouter une stratégie de nouvelle tentative. Les requêtes qui ont échoué sont retentées jusqu’à trois fois. Le deuxième appel à AddTransientHttpErrorPolicy
ajoute une stratégie de disjoncteur. Les requêtes externes supplémentaires sont bloquées pendant 30 secondes si cinq tentatives successives échouent. Les stratégies de disjoncteur sont avec état. Tous les appels effectués via ce client partagent le même état du circuit.
Ajouter des stratégies à partir du Registre Polly
Une approche de la gestion des stratégies régulièrement utilisées consiste à les définir une seule fois et à les inscrire avec un PolicyRegistry
. Il existe une méthode d’extension qui permet l’ajout d’un gestionnaire avec une stratégie du Registre :
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regulartimeouthandler")
.AddPolicyHandlerFromRegistry("regular");
Dans le code précédent, deux stratégies sont inscrites lorsque PolicyRegistry
est ajouté à ServiceCollection
. Pour utiliser une stratégie du Registre, la méthode AddPolicyHandlerFromRegistry
est utilisée, en passant le nom de la stratégie à appliquer.
Vous trouverez plus d’informations sur les intégrations de IHttpClientFactory
et de Polly dans le wiki Polly.
HttpClient et gestion de la durée de vie
Une nouvelle instance HttpClient
est retournée à chaque fois que CreateClient
est appelé sur IHttpClientFactory
. Il existe un HttpMessageHandler par client nommé. La fabrique gère les durées de vie des instances HttpMessageHandler
.
IHttpClientFactory
regroupe dans un pool les instances de HttpMessageHandler
créées par la fabrique pour réduire la consommation des ressources. Une instance de HttpMessageHandler
peut être réutilisée à partir du pool quand vous créez une instance de HttpClient
si sa durée de vie n’a pas expiré.
Le regroupement de gestionnaires en pools est souhaitable, car chaque gestionnaire gère habituellement ses propres connexions HTTP sous-jacentes. La création de plus de gestionnaires que nécessaire peut entraîner 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. La valeur par défaut peut être remplacée pour chaque client nommé. Pour la remplacer, appelez SetHandlerLifetime sur le IHttpClientBuilder
qui est retourné lors de la création du client :
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
La suppression du client n’est pas nécessaire. La suppression annule les requêtes sortantes et garantit que l’instance HttpClient
donnée ne peut pas être utilisée après avoir appelé Dispose. IHttpClientFactory
effectue le suivi et libère les ressources utilisées par les instances HttpClient
. Les instances HttpClient
peuvent généralement être traitées en tant qu’objets .NET ne nécessitant pas une suppression.
Le fait de conserver une seule instance HttpClient
active pendant une longue durée est un modèle commun utilisé avant le lancement de IHttpClientFactory
. Ce modèle devient inutile après la migration vers IHttpClientFactory
.
Alternatives à IHttpClientFactory
L’utilisation de IHttpClientFactory
dans une application avec injection de dépendances évite :
- Les problèmes d’épuisement des ressources en regroupant les instances
HttpMessageHandler
. - Les problèmes de DNS obsolète en permutant les instances
HttpMessageHandler
à intervalles réguliers.
Il existe d’autres façons de résoudre les problèmes précédents à l’aide d’une instance SocketsHttpHandler de longue durée.
- Créez une instance de
SocketsHttpHandler
lorsque l’application démarre et utilisez-la pour la durée de vie de l’application. - Configurez PooledConnectionLifetime sur une valeur appropriée en fonction des temps d’actualisation DNS.
- Créez des instances
HttpClient
à l’aide denew HttpClient(handler, disposeHandler: false)
si nécessaire.
Les approches précédentes résolvent les problèmes de gestion des ressources que IHttpClientFactory
résout de la même manière.
- Le
SocketsHttpHandler
partage les connexions entre les instancesHttpClient
. Ce partage empêche l’épuisement des sockets. - Le
SocketsHttpHandler
permute les connexions en fonction dePooledConnectionLifetime
pour éviter les problèmes de DNS obsolète.
Cookies
Le regroupement des instances HttpMessageHandler
engendre le partage d’objets CookieContainer
. Le partage d’objets CookieContainer
imprévus entraîne souvent un code incorrect. Pour les applications qui nécessitent des cookies, tenez compte de ce qui suit :
- Désactivation de la gestion de cookie automatique
- Éviter
IHttpClientFactory
Appelez ConfigurePrimaryHttpMessageHandler pour désactiver la gestion de cookie automatique :
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Journalisation
Les clients créés via IHttpClientFactory
enregistrent les messages de journalisation pour toutes les requêtes. Activez le niveau d’informations approprié dans votre configuration de journalisation pour voir les messages de journalisation par défaut. Une journalisation supplémentaire, comme celle des en-têtes des requêtes, est incluse seulement au niveau de trace.
La catégorie de journal utilisée pour chaque client comprend le nom du client. Par exemple, un client nommé MyNamedClient journalise les messages avec la catégorie System.Net.Http.HttpClient.MyNamedClient.LogicalHandler
. Les messages avec le suffixe LogicalHandler se produisent en dehors du pipeline du gestionnaire de requêtes. Lors d’une requête, les messages sont journalisés avant que d’autres gestionnaires du pipeline l’aient traitée. Lors d’une réponse, les messages sont journalisés une fois que tous les autres gestionnaires du pipeline ont reçu la réponse.
La journalisation se produit également à l’intérieur du pipeline du gestionnaire de requêtes. Dans l’exemple MyNamedClient, ces messages sont journalisés avec la catégorie de journalisation System.Net.Http.HttpClient.MyNamedClient.ClientHandler
. Pour la requête, cela se produit après que tous les autres gestionnaires ont été exécutés et immédiatement avant l’envoi de la requête sur le réseau. Lors de la réponse, cette journalisation inclut l’état de la réponse avant qu’elle repasse à travers le pipeline de gestionnaires.
L’activation de la journalisation à l’extérieur et à l’intérieur du pipeline permet l’inspection des changements apportés par les autres gestionnaires du pipeline. Par exemple, cela peut comprendre des changements apportés aux en-têtes des requêtes ou au code d’état de la réponse.
L’ajout du nom du client dans la catégorie de journalisation permet si nécessaire de filtrer le journal pour des clients nommés spécifiques.
Configurer le HttpMessageHandler
Il peut être nécessaire de contrôler la configuration du HttpMessageHandler
interne utilisé par un client.
Un IHttpClientBuilder
est retourné quand vous ajoutez des clients nommés ou typés. La méthode d’extension ConfigurePrimaryHttpMessageHandler peut être utilisée pour définir un délégué. Le délégué est utilisé pour créer et configurer le HttpMessageHandler
principal utilisé par ce client :
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
Utiliser IHttpClientFactory dans une application console
Dans une application console, ajoutez les références de package suivantes au projet :
Dans l’exemple suivant :
- IHttpClientFactory est inscrit dans le conteneur de service dans de l’hôte générique.
MyService
crée une instance de fabrique cliente à partir du service, qui est utilisée pour créer unHttpClient
.HttpClient
est utilisé pour récupérer une page web.- La méthode
GetPage
du service est exécutée pour écrire les 500 premiers caractères du contenu de la page web dans la console. Pour plus d’informations sur l’appel de services à partir deProgram.Main
, consultez Injection de dépendances dans 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}";
}
}
}
}
Intergiciel de propagation d’en-tête
La propagation d’en-tête est un intergiciel pris en charge par la communauté pour propager les en-têtes HTTP de la requête entrante vers les requêtes client HTTP sortantes. Pour utiliser la propagation d’en-tête :
Référencez le portage pris en charge par la communauté du package HeaderPropagation. ASP.NET Core 3.1 et versions ultérieures prennent en charge Microsoft.AspNetCore.HeaderPropagation.
Configurez l’intergiciel et
HttpClient
dansStartup
: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(); }
Le client inclut les en-têtes configurés sur les requêtes sortantes :
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);