IHttpClientFactory avec .NET

Dans cet article, vous allez apprendre à utiliser le IHttpClientFactory pour créer des types HttpClient avec différents principes fondamentaux de .NET, comme l’injection de dépendances (DI), la journalisation et la configuration. Le type HttpClient a été introduit dans .NET Framework 4.5, qui a été publié en 2012. En d’autres termes, il existe déjà depuis un moment. HttpClient est utilisé pour effectuer des requêtes HTTP et gérer les réponses HTTP à partir de ressources web identifiées par un Uri. Le protocole HTTP couvre la grande majorité de tout le trafic Internet.

Avec les principes de développement d’applications modernes à l’origine des meilleures pratiques, le IHttpClientFactory sert d’abstraction d’usine qui peut créer des instances HttpClient avec des configurations personnalisées. IHttpClientFactory a été introduit dans .NET Core 2.1. Les charges de travail .NET courantes basées sur HTTP peuvent tirer facilement parti des intergiciels tiers de gestion des erreurs temporaires et résilients.

Notes

Si votre application nécessite des cookies, il peut être préférable d’éviter d’utiliser IHttpClientFactory dans votre application. Pour obtenir d’autres méthodes de gestion des clients, consultez Recommandations pour l’utilisation des clients HTTP.

Important

La gestion de la durée de vie des instances HttpClient créées par IHttpClientFactory est complètement différente des instances créées manuellement. Les stratégies sont d’utiliser des clients de courte durée créés par IHttpClientFactory ou des clients de longue durée avec configuration de PooledConnectionLifetime. Pour plus d’informations, consultez la section Gestion de la durée de vie de HttpClient et Instructions pour l’utilisation de clients HTTP.

Le type IHttpClientFactory

Tout l’exemple de code source de cet article s’appuie sur le package NuGet Microsoft.Extensions.Http. En outre, les requêtes HTTP GET sont envoyées à l’API d’espace réservé {JSON} gratuite pour obtenir des objets utilisateur Todo.

Lorsque vous appelez l’une des méthodes d’extension AddHttpClient, vous ajoutez le IHttpClientFactory et les services associés au IServiceCollection. Le type IHttpClientFactory offre les avantages suivants :

  • Expose la classe HttpClient sous la forme d’un type prêt pour l’intégration de données.
  • Fournit un emplacement central pour le nommage et la configuration d’instance de HttpClient logiques.
  • Codifie le concept d’intergiciel (middleware) sortant via la délégation de gestionnaires dans HttpClient.
  • Fournit des méthodes d’extension pour les intergiciels basés sur Polly afin de tirer parti de la délégation de gestionnaires dans HttpClient.
  • Gère la mise en cache et la durée de vie des instances HttpClientHandler sous-jacentes. La gestion automatique évite les problèmes courants liés au système DNS (Domain Name System) qui se produisent lors de la gestion manuelle des durées de vie HttpClient.
  • Ajoute une expérience de journalisation configurable (via ILogger) pour toutes les requêtes envoyées via des clients créés par la fabrique.

Modèles de consommation

Vous pouvez utiliser IHttpClientFactory dans une application de plusieurs façons :

La meilleure approche dépend des exigences de l’application.

Utilisation de base

Pour inscrire le IHttpClientFactory, appelez AddHttpClient :

using Shared;
using BasicHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHttpClient();
builder.Services.AddTransient<TodoService>();

using IHost host = builder.Build();

La consommation de services peut nécessiter le IHttpClientFactory en tant que paramètre de constructeur avec DI. Le code suivant utilise IHttpClientFactory pour créer une instance HttpClient :

using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;

namespace BasicHttp.Example;

public sealed class TodoService(
    IHttpClientFactory httpClientFactory,
    ILogger<TodoService> logger)
{
    public async Task<Todo[]> GetUserTodosAsync(int userId)
    {
        // Create the client
        using HttpClient client = httpClientFactory.CreateClient();
        
        try
        {
            // Make HTTP GET request
            // Parse JSON response deserialize into Todo types
            Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
                $"https://jsonplaceholder.typicode.com/todos?userId={userId}",
                new JsonSerializerOptions(JsonSerializerDefaults.Web));

            return todos ?? [];
        }
        catch (Exception ex)
        {
            logger.LogError("Error getting something fun to say: {Error}", ex);
        }

        return [];
    }
}

L’utilisation de IHttpClientFactory comme dans l’exemple précédent est un bon moyen de refactoriser une application existante. Cela n’a aucun impact sur la façon dont HttpClient est utilisé. Aux endroits où des instances HttpClient sont créées dans une application existante, remplacez ces occurrences par des appels à CreateClient.

Clients nommés

Les clients nommés sont un bon choix dans les cas suivants :

  • L’application nécessite de nombreuses utilisations distinctes de HttpClient.
  • De nombreuses instances de HttpClient ont des configurations différentes.

La configuration d’un HttpClient nommé peut être spécifiée lors de l’inscription dans IServiceCollection :

using Shared;
using NamedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

string? httpClientName = builder.Configuration["TodoHttpClientName"];
ArgumentException.ThrowIfNullOrEmpty(httpClientName);

builder.Services.AddHttpClient(
    httpClientName,
    client =>
    {
        // Set the base address of the named client.
        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");

        // Add a user-agent default request header.
        client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
    });

Dans le code précédent, le client est configuré avec :

  • Nom extrait de la configuration sous le "TodoHttpClientName".
  • L’adresse de base https://jsonplaceholder.typicode.com/.
  • En-tête de "User-Agent".

Vous pouvez utiliser la configuration pour spécifier des noms de clients HTTP, ce qui est utile pour éviter les erreurs de nommage des clients lors de l’ajout et de la création. Dans cet exemple, le fichier appsettings.json est utilisé pour configurer le nom du client HTTP :

{
    "TodoHttpClientName": "JsonPlaceholderApi"
}

Il est facile d’étendre cette configuration et de stocker plus de détails sur le fonctionnement de votre client HTTP. Pour plus d’informations, consultez Configuration dans .NET.

Créer un client

Chaque fois que CreateClient est appelé :

  • Une nouvelle instance de HttpClient est créée.
  • L’action de configuration est appelée.

Pour créer un client nommé, passez son nom dans CreateClient :

using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Shared;

namespace NamedHttp.Example;

public sealed class TodoService
{
    private readonly IHttpClientFactory _httpClientFactory = null!;
    private readonly IConfiguration _configuration = null!;
    private readonly ILogger<TodoService> _logger = null!;

    public TodoService(
        IHttpClientFactory httpClientFactory,
        IConfiguration configuration,
        ILogger<TodoService> logger) =>
        (_httpClientFactory, _configuration, _logger) =
            (httpClientFactory, configuration, logger);

    public async Task<Todo[]> GetUserTodosAsync(int userId)
    {
        // Create the client
        string? httpClientName = _configuration["TodoHttpClientName"];
        using HttpClient client = _httpClientFactory.CreateClient(httpClientName ?? "");

        try
        {
            // Make HTTP GET request
            // Parse JSON response deserialize into Todo type
            Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
                $"todos?userId={userId}",
                new JsonSerializerOptions(JsonSerializerDefaults.Web));

            return todos ?? [];
        }
        catch (Exception ex)
        {
            _logger.LogError("Error getting something fun to say: {Error}", ex);
        }

        return [];
    }
}

Dans le code précédent, la requête HTTP n’a pas besoin de spécifier un nom d’hôte. Le code peut simplement passer le chemin, car l’adresse de base configurée pour le client est utilisée.

Clients typés

Clients typés :

  • Fournissent les mêmes fonctionnalités que les clients nommés, sans qu’il soit nécessaire d’utiliser des chaînes comme clés.
  • Fournissez IntelliSense et l’aide du compilateur lors de la consommation de clients.
  • 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 :

using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;

namespace TypedHttp.Example;

public sealed class TodoService(
    HttpClient httpClient,
    ILogger<TodoService> logger) : IDisposable
{
    public async Task<Todo[]> GetUserTodosAsync(int userId)
    {
        try
        {
            // Make HTTP GET request
            // Parse JSON response deserialize into Todo type
            Todo[]? todos = await httpClient.GetFromJsonAsync<Todo[]>(
                $"todos?userId={userId}",
                new JsonSerializerOptions(JsonSerializerDefaults.Web));

            return todos ?? [];
        }
        catch (Exception ex)
        {
            logger.LogError("Error getting something fun to say: {Error}", ex);
        }

        return [];
    }

    public void Dispose() => httpClient?.Dispose();
}

Dans le code précédent :

  • La configuration est définie lorsque le client typé est ajouté à la collection de services.
  • Le HttpClient est affecté en tant que variable (champ) à étendue de classe, et utilisé avec des API exposées.

Vous pouvez créer des méthodes spécifiques à l’API qui exposent des fonctionnalités de HttpClient. Par exemple, la méthode GetUserTodosAsync encapsule du code pour récupérer des objets Todo spécifiques à l’utilisateur.

Le code suivant appelle AddHttpClient pour inscrire une classe cliente typée :

using Shared;
using TypedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHttpClient<TodoService>(
    client =>
    {
        // Set the base address of the typed client.
        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");

        // Add a user-agent default request header.
        client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
    });

Le client typé est inscrit comme étant transitoire avec injection de dépendances. Dans le code précédent, AddHttpClient inscrit TodoService en tant que service temporaire. Cette inscription utilise une méthode de fabrique pour :

  1. Créez une instance de HttpClient.
  2. Créer une instance de TodoService en passant l’instance de HttpClient à son constructeur.

Important

L’utilisation de clients typés dans les services singleton peut être dangereuse. Pour plus d’informations, consultez la section Éviter les clients typés dans les services singleton.

Notes

Lors de l’inscription d’un client typé avec la méthode AddHttpClient<TClient>, le type TClient doit avoir un constructeur qui accepte un paramètre HttpClient. En outre, le type de TClient ne doit pas être inscrit auprès du conteneur d’adresses de base de données séparément.

Clients nommés et typés

Les clients nommés et les clients typés ont leurs propres forces et faiblesses. Il existe un moyen de combiner ces deux types de clients pour obtenir le meilleur des deux mondes.

Le cas d’usage principal est le suivant : utilisez le même client typé, mais sur différents domaines. Par exemple, vous avez un service principal et un service secondaire, et ils exposent exactement la même fonctionnalité. Cela signifie que vous pouvez utiliser le même client typé pour envelopper l’utilisation de HttpClient, et envoyer des demandes, traiter les réponses et gérer les erreurs. Le même code est utilisé, mais avec différentes configurations (adresse de base, délai d’expiration et informations d’identification différents, par exemple).

L’exemple suivant utilise le même client typé TodoService qui s’affiche sous la section des clients typés.

Inscrivez d’abord les clients nommés et typés.

using Shared;
using TypedHttp.Example;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHttpClient<TodoService>("primary"
    client =>
    {
        // Configure the primary typed client
        client.BaseAddress = new Uri("https://primary-host-address.com/");
        client.Timeout = TimeSpan.FromSeconds(3);
    });

// Register the same typed client but with different settings
builder.Services.AddHttpClient<TodoService>("secondary"
    client =>
    {
        // Configure the secondary typed client
        client.BaseAddress = new Uri("https://secondary-host-address.com/");
        client.Timeout = TimeSpan.FromSeconds(10);
    });

Dans le code précédent :

  • Le premier appel d’AddHttpClient inscrit un client typé TodoService sous le nom primary. Le HttpClient sous-jacent pointe vers le service principal et a un délai d’expiration court.
  • Le deuxième appel d’AddHttpClient inscrit un client typé TodoService sous le nom secondary. Le HttpClient sous-jacent pointe vers le service secondaire et a un délai d’expiration plus long.
using IHost host = builder.Build();

// Fetch an IHttpClientFactory instance to create a named client
IHttpClientFactory namedClientFactory =
    host.Services.GetRequiredService<IHttpClientFactory>();

// Fetch an ITypedHttpClientFactory<TodoService> instance to create a named and typed client
ITypedHttpClientFactory<TodoService> typedClientFactory  =
    host.Services.GetRequiredService<ITypedHttpClientFactory<TodoService>>();

// Create a TodoService instance against the primary host
var primaryClient = namedClientFactory.CreateClient("primary");
var todoService = typedClientFactory.CreateClient(primaryClient);

Dans le code précédent :

  • Une instance de IHttpClientFactory est récupérée à partir du conteneur DI pour pouvoir créer des clients nommés via la méthode CreateClient.
  • Une instance de ITypedHttpClientFactory<TodoService> est récupérée à partir du conteneur DI pour pouvoir créer des clients typés via la méthode CreateClient.
    • Cette surcharge CreateClient a reçu un HttpClient nommé (avec la configuration appropriée) comme paramètre.
    • Le todoService créé est configuré pour utiliser le service principal.

Remarque

Le type IHttpClientFactory réside dans les espaces de noms System.Net.Http, tandis que le type ITypedHttpClientFactory est à l’intérieur de Microsoft.Extensions.Http.

Important

Utilisez la classe d’implémentation (dans l’exemple précédent, TodoService) comme paramètre de type de ITypedHttpClientFactory. Même si vous avez également une abstraction (comme l’interface ITodoService), vous devez quand même utiliser l’implémentation. Si vous utilisez accidentellement l’abstraction (ITodoService), quand vous appelez sa méthode CreateClient, elle lève une exception InvalidOperationException.

try
{
    Todo[] todos = await todoService.GetUserTodosAsync(4);
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
    // The request timed out against the primary host

    // Create a TodoService instance against the secondary host
    var fallbackClient = namedClientFactory.CreateClient("secondary");
    var todoFallbackService = typedClientFactory.CreateClient(fallbackClient);

    // Issue request against the secondary host
    Todo[] todos = await todoFallbackService.GetUserTodosAsync(4);
}

Dans le code précédent :

  • Elle tente d’envoyer une demande sur le service principal.
  • Si la demande expire (plus de 3 secondes), elle lève une exception TaskCanceledException avec une exception TimeoutException interne.
  • En cas d’expiration, un nouveau client est créé et utilisé pour cibler le service secondaire cette fois.

Clients générés

IHttpClientFactory peut être utilisé en combinaison avec des bibliothèques tierces, comme Refit. Refit est une bibliothèque REST pour .NET. Cela permet des définitions d’API REST déclaratives, en mappant les méthodes d’interface aux points de terminaison. Une implémentation de l’interface est générée dynamiquement par le RestService, avec HttpClient pour faire les appels HTTP externes.

Considérez le type de record suivant :

namespace Shared;

public record class Todo(
    int UserId,
    int Id,
    string Title,
    bool Completed);

L’exemple suivant s’appuie sur le package NuGet Refit.HttpClientFactory et est une interface simple :

using Refit;
using Shared;

namespace GeneratedHttp.Example;

public interface ITodoService
{
    [Get("/todos?userId={userId}")]
    Task<Todo[]> GetUserTodosAsync(int userId);
}

L’interface C# précédente :

  • Définit une méthode nommée GetUserTodosAsync qui retourne une instance de Task<Todo[]>.
  • Déclare un attribut Refit.GetAttribute avec le chemin d’accès et la chaîne de requête à l’API externe.

Vous pouvez ajouter un client typé en utilisant Refit pour générer l’implémentation :

using GeneratedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Refit;
using Shared;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddRefitClient<ITodoService>()
    .ConfigureHttpClient(client =>
    {
        // Set the base address of the named client.
        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");

        // Add a user-agent default request header.
        client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
    });

L’interface définie peut être utilisée quand c’est nécessaire, avec l’implémentation fournie par l’injection de dépendances et Refit.

Effectuer des requêtes POST, PUT et DELETE

Dans les exemples précédents, toutes les requêtes HTTP utilisent le verbe HTTP GET. HttpClient prend également en charge d’autres verbes HTTP, notamment :

  • POST
  • PUT
  • DELETE
  • PATCH

Pour obtenir la liste complète des verbes HTTP pris en charge, consultez HttpMethod. Pour plus d’informations sur l’envoi de requêtes HTTP, consultez Envoyer une requête à l’aide de HttpClient.

L’exemple suivant montre comment effectuer une requête HTTP POST :

public async Task CreateItemAsync(Item item)
{
    using StringContent json = new(
        JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
        Encoding.UTF8,
        MediaTypeNames.Application.Json);

    using HttpResponseMessage httpResponse =
        await httpClient.PostAsync("/api/items", json);

    httpResponse.EnsureSuccessStatusCode();
}

Dans le code précédent, la méthode CreateItemAsync :

  • Sérialise le paramètre Item au format JSON à l’aide de System.Text.Json. Cela utilise une instance de JsonSerializerOptions pour configurer le processus de sérialisation.
  • Crée une instance de StringContent pour empaqueter le JSON sérialisé pour l’envoi dans le corps de la requête HTTP.
  • Appelle PostAsync pour envoyer le contenu JSON à l’URL spécifiée. Il s’agit d’une URL relative qui est ajoutée à HttpClient.BaseAddress.
  • Appelle EnsureSuccessStatusCode pour lever une exception si le code d’état de la réponse n’indique pas la réussite.

HttpClient prend également en charge d’autres types de contenu. Par exemple : MultipartContent et StreamContent. Pour obtenir la liste complète du contenu pris en charge, consultez HttpContent.

L’exemple suivant montre une requête HTTP PUT :

public async Task UpdateItemAsync(Item item)
{
    using StringContent json = new(
        JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
        Encoding.UTF8,
        MediaTypeNames.Application.Json);

    using HttpResponseMessage httpResponse =
        await httpClient.PutAsync($"/api/items/{item.Id}", json);

    httpResponse.EnsureSuccessStatusCode();
}

Le code précédent est très similaire à l’exemple POST. La méthode UpdateItemAsync appelle PutAsync au lieu de PostAsync.

L’exemple suivant montre une requête HTTP DELETE :

public async Task DeleteItemAsync(Guid id)
{
    using HttpResponseMessage httpResponse =
        await httpClient.DeleteAsync($"/api/items/{id}");

    httpResponse.EnsureSuccessStatusCode();
}

Dans le code précédent, la méthode DeleteItemAsync appelle DeleteAsync. Étant donné que les requêtes HTTP DELETE ne contiennent généralement aucun corps, la 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.

Gestion de la durée de vie HttpClient

Une nouvelle instance HttpClient est retournée à chaque fois que CreateClient est appelé sur IHttpClientFactory. Une instance HttpClientHandler est créée par nom de client. La fabrique gère les durées de vie des instances HttpClientHandler.

IHttpClientFactory met en cache les instances de HttpClientHandler créées par la fabrique pour réduire la consommation des ressources. Une instance de HttpClientHandler peut être réutilisée à partir du cache quand vous créez une instance de HttpClient si sa durée de vie n’a pas expiré.

La mise en cache de gestionnaires en pools est souhaitable, car chaque gestionnaire gère habituellement son propre pool de connexions HTTP sous-jacentes. La création de plus de gestionnaires que nécessaire peut entraîner des épuisements de sockets et des délais de connexion. Certains gestionnaires conservent aussi les connexions indéfiniment ouvertes, ce qui peut empêcher le gestionnaire de réagir aux changements du DNS.

La durée de vie par défaut d’un gestionnaire est de deux minutes. Pour remplacer la valeur par défaut, appelez SetHandlerLifetime pour chaque client, sur le IServiceCollection :

services.AddHttpClient("Named.Client")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

Important

Les instances HttpClient créées par IHttpClientFactory sont destinées à être de courte durée.

  • Le recyclage et la recréation des HttpMessageHandler à l’expiration de leur durée de vie sont essentiels pour que IHttpClientFactory puisse garantir que les gestionnaires réagissent aux modifications DNS. HttpClient étant lié à une instance de gestionnaire spécifique lors de sa création, de nouvelles instances HttpClient doivent être demandées en temps opportun pour garantir que le client obtiendra le gestionnaire mis à jour.

  • La suppression de telles instances de HttpClientcréées par la fabrique n’entraîne pas d’épuisement du socket, car son d’élimination ne déclenche pas l’élimination du HttpMessageHandler. IHttpClientFactory effectue le suivi et l’élimination des ressources utilisées pour créer des instances HttpClient, en particulier les instances HttpMessageHandler, dès que leur durée de vie expire et qu’il n’y a plus de HttpClient les utilisant.

Le maintien d’une instance unique de HttpClient en vie pendant une longue durée est un modèle courant qui peut être utilisé comme alternative à IHttpClientFactory. Cependant, ce modèle nécessite une configuration supplémentaire, comme PooledConnectionLifetime. Vous pouvez utiliser des clients de longue durée avec PooledConnectionLifetime, ou des clients à courte durée créés par IHttpClientFactory. Pour plus d’informations sur la stratégie à utiliser dans votre application, consultez Recommandations pour l’utilisation des clients HTTP.

Configurer le HttpMessageHandler

Il peut être nécessaire de contrôler la configuration du HttpMessageHandler interne utilisé par un client.

Un IHttpClientBuilder est retourné 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é sur le IServiceCollection. Le délégué est utilisé pour créer et configurer le HttpMessageHandler principal utilisé par ce client :

.ConfigurePrimaryHttpMessageHandler(() =>
{
    return new HttpClientHandler
    {
        AllowAutoRedirect = false,
        UseDefaultCredentials = true
    };
});

La configuration de HttClientHandler vous permet de spécifier un proxy pour l’instance HttpClient entre autres propriétés du gestionnaire. Pour plus d’informations, consultez Proxy par client.

Configuration supplémentaire

Il existe plusieurs options de configuration supplémentaires pour contrôler le IHttpClientHandler :

Méthode Description
AddHttpMessageHandler Ajoute un gestionnaire de messages supplémentaire pour un HttpClient nommé.
AddTypedClient Configure la liaison entre le TClient et le HttpClient nommé associés à IHttpClientBuilder.
ConfigureHttpClient Ajoute un délégué utilisé pour configurer un HttpClient nommé.
ConfigureHttpMessageHandlerBuilder Ajoute un délégué qui sera utilisé afin de configurer des gestionnaires de messages à l’aide de HttpMessageHandlerBuilder pour un HttpClient nommé.
ConfigurePrimaryHttpMessageHandler Configure le HttpMessageHandler principal à partir du conteneur d’injection de dépendances pour un HttpClient nommé.
RedactLoggedHeaders Définit la collection de noms d’en-têtes HTTP pour lesquels les valeurs doivent être modifiées avant la journalisation.
SetHandlerLifetime Définit la durée pendant laquelle une instance de HttpMessageHandler peut être réutilisée. Chaque client nommé peut avoir sa propre valeur de durée de vie de gestionnaire configurée.

Utilisation de IHttpClientFactory avec SocketsHttpHandler

L’implémentation SocketsHttpHandler de HttpMessageHandler a été ajoutée dans .NET Core 2.1, ce qui permet à PooledConnectionLifetime d’être configuré. Ce paramètre est utilisé pour garantir que le gestionnaire réagit aux modifications DNS. L’utilisation de SocketsHttpHandler est donc considérée comme une alternative à l’utilisation de IHttpClientFactory. Pour plus d’informations, consultez Instructions relatives à l’utilisation de clients HTTP.

Toutefois, SocketsHttpHandler et IHttpClientFactory peuvent être utilisés ensemble pour améliorer la configurabilité. En utilisant ces deux API, vous bénéficiez de la configurabilité à un niveau faible (par exemple, en utilisant LocalCertificateSelectionCallback pour la sélection dynamique de certificats) et à un niveau élevé (par exemple, en tirant parti de l’intégration de l’injection de dépendances et de plusieurs configurations clientes).

Pour utiliser les deux API :

  1. Spécifiez SocketsHttpHandler en tant que PrimaryHandler et configurez son PooledConnectionLifetime (par exemple, sur une valeur qui était précédemment dans HandlerLifetime).
  2. Comme SocketsHttpHandler gérera le regroupement et le recyclage des connexions, le recyclage des gestionnaires au niveau IHttpClientFactory n’est plus nécessaire. Vous pouvez le désactiver en définissant HandlerLifetime sur Timeout.InfiniteTimeSpan.
services.AddHttpClient(name)
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new SocketsHttpHandler()
        {
            PooledConnectionLifetime = TimeSpan.FromMinutes(2)
        };
    })
    .SetHandlerLifetime(Timeout.InfiniteTimeSpan); // Disable rotation, as it is handled by PooledConnectionLifetime

Éviter les clients typés dans les services singleton

Lorsque vous utilisez l’approche de client nommé, IHttpClientFactory est injecté dans les services et des instances HttpClient sont créées en appelant CreateClient chaque fois qu’un HttpClient est nécessaire.

Toutefois, avec l’approche de client typé, les clients typés sont des objets temporaires généralement injectés dans les services. Cela peut causer un problème, car un client typé peut être injecté dans un service singleton.

Important

Les clients typés sont censés être de courte durée dans le même sens que les instances HttpClient créées par IHttpClientFactory (pour plus d’informations, consultez Gestion de la durée de vie de HttpClient). Dès qu’une instance de client typé est créée, IHttpClientFactory n’a aucun contrôle sur celle-ci. Si une instance de client typée est capturée dans un singleton, elle peut l’empêcher de réagir aux modifications DNS, ce qui va à l’encontre de l’un des objectifs de IHttpClientFactory.

Si vous devez utiliser des instances HttpClient dans un service singleton, envisagez les options suivantes :

  • Utilisez plutôt l’approche de client nommé, en injectant IHttpClientFactory dans le service singleton et en recréant des instances HttpClient si nécessaire.
  • Si vous avez besoin de l’approche de client typé, utilisez SocketsHttpHandler avec le PooledConnectionLifetime configuré comme gestionnaire principal. Pour plus d’informations sur l’utilisation de SocketsHttpHandler avec IHttpClientFactory, consultez la section Utilisation de IHttpClientFactory avec SocketsHttpHandler.

Étendues du gestionnaire de messages dans IHttpClientFactory

IHttpClientFactory crée une étendue d’ID distincte pour chaque instance HttpMessageHandler. Ces étendues d’ID sont distinctes des étendues d’injection de dépendances d’application (par exemple, étendue de requête entrante ASP.NET ou étendue d’injection de dépendances manuelle créée par l’utilisateur), ainsi elles ne partagent pas d’instances de service délimitées. Les étendues du gestionnaire de messages sont liées à la durée de vie du gestionnaire et peuvent survivre aux étendues de l’application, ce qui peut entraîner, par exemple, la réutilisation de la même instance de HttpMessageHandler avec les mêmes dépendances injectées étendues entre plusieurs requêtes entrantes.

Diagramme montrant deux étendues d’ID d’application et une étendue de gestionnaire de messages distincte

Il est vivement recommandé aux utilisateurs de ne pas mettre en cache les informations liées à l’étendue (comme les données de HttpContext) à l’intérieur des instances HttpMessageHandler et d’utiliser les dépendances délimitées avec précaution pour éviter les fuites d’informations sensibles.

Si vous avez besoin d’accéder à une étendue d’injection de dépendances d’application à partir de votre gestionnaire de messages, par exemple, pour l’authentification, vous devez encapsuler une logique prenant en charge l’étendue dans un DelegatingHandler temporaire distinct et le wrapper autour d’une instance HttpMessageHandler du cache IHttpClientFactory. Pour accéder au gestionnaire, appelez IHttpMessageHandlerFactory.CreateHandler pour tout client nommé inscrit. Dans ce cas, vous devez créer une instance HttpClient vous-même en utilisant le gestionnaire construit.

Diagramme montrant l’accès aux étendues d’injection de dépendances d’application via un gestionnaire de messages temporaire distinct et IHttpMessageHandlerFactory

L’exemple suivant montre la création d’un HttpClient avec un DelegatingHandler prenant en charge l’étendue :

if (scopeAwareHandlerType != null)
{
    if (!typeof(DelegatingHandler).IsAssignableFrom(scopeAwareHandlerType))
    {
        throw new ArgumentException($"""
            Scope aware HttpHandler {scopeAwareHandlerType.Name} should
            be assignable to DelegatingHandler
            """);
    }

    // Create top-most delegating handler with scoped dependencies
    scopeAwareHandler = (DelegatingHandler)_scopeServiceProvider.GetRequiredService(scopeAwareHandlerType); // should be transient
    if (scopeAwareHandler.InnerHandler != null)
    {
        throw new ArgumentException($"""
            Inner handler of a delegating handler {scopeAwareHandlerType.Name} should be null.
            Scope aware HttpHandler should be registered as Transient.
            """);
    }
}

// Get or create HttpMessageHandler from HttpClientFactory
HttpMessageHandler handler = _httpMessageHandlerFactory.CreateHandler(name);

if (scopeAwareHandler != null)
{
    scopeAwareHandler.InnerHandler = handler;
    handler = scopeAwareHandler;
}

HttpClient client = new(handler);

Une autre solution de contournement peut utiliser une méthode d’extension pour inscrire un DelegatingHandler prenant en charge l’étendue et remplacer l’inscription par défaut IHttpClientFactory par un service temporaire ayant accès à l’étendue d’application actuelle :

public static IHttpClientBuilder AddScopeAwareHttpHandler<THandler>(
    this IHttpClientBuilder builder) where THandler : DelegatingHandler
{
    builder.Services.TryAddTransient<THandler>();
    if (!builder.Services.Any(sd => sd.ImplementationType == typeof(ScopeAwareHttpClientFactory)))
    {
        // Override default IHttpClientFactory registration
        builder.Services.AddTransient<IHttpClientFactory, ScopeAwareHttpClientFactory>();
    }

    builder.Services.Configure<ScopeAwareHttpClientFactoryOptions>(
        builder.Name, options => options.HttpHandlerType = typeof(THandler));

    return builder;
}

Pour plus d’informations, consultez l’exemple complet.

Voir aussi