Injection de dépendance de plusieurs instances du même type dans ASP.NET Core 2

Dans l’API Web ASP.NET Core 2, je souhaite utiliser l’dependency injection pour injecter l’instance HttpClient de HttpClient à ControllerA , et une instance httpClientB de HttpClient à ControllerB .

Le code d’enregistrement de la DI ressemblerait à quelque chose comme:

 HttpClient httpClientA = new HttpClient(); httpClientA.BaseAddress = endPointA; services.AddSingleton(httpClientA); HttpClient httpClientB = new HttpClient(); httpClientB.BaseAddress = endPointB; services.AddSingleton(httpClientB); 

Je sais que je pourrais sous- HttpClient pour créer un type unique pour chaque contrôleur, mais cela ne s’adapte pas très bien.

Quel est un meilleur moyen?

MISE À JOUR Spécifiquement concernant HttpClient Microsoft semble avoir quelque chose en préparation

https://github.com/aspnet/HttpClientFactory/blob/dev/samples/HttpClientFactorySample/Program.cs#L32 – merci à @ mountain-traveller (Dylan) pour l’avoir signalé.

Remarque: cette réponse utilise HttpClient et un HttpClientFactory titre d’ exemple, mais s’applique facilement à tout autre type de chose. Pour HttpClient en particulier, l’ utilisation du nouveau IHttpClientFactory de Microsoft.Extensions.Http est préférable.


Le conteneur d’dependency injection intégré ne prend pas en charge les enregistrements de dépendance nommés, et il n’est pas prévu de l’append pour le moment .

Une des raisons à cela est qu’avec l’dependency injection, il n’y a pas de moyen de spécifier le type d’instance nommée que vous souhaitez. Vous pouvez sûrement utiliser quelque chose comme des atsortingbuts de paramètre pour les constructeurs (ou des atsortingbuts sur des propriétés pour l’injection de propriétés), mais ce serait un type de complexité différent qui ne serait probablement pas rentable. et il ne serait certainement pas soutenu par le système de types , qui est une partie importante du fonctionnement de l’dependency injection.

En général, les dépendances nommées sont un signe que vous ne concevez pas correctement vos dépendances. Si vous avez deux dépendances différentes du même type, cela signifie qu’elles peuvent être utilisées de manière interchangeable. Si ce n’est pas le cas et que l’un d’eux est valable alors que l’autre ne l’est pas, c’est le signe que vous enfreignez peut-être le principe de substitution de Liskov .

En outre, si vous examinez les injections de dépendance qui prennent en charge les dépendances nommées, vous remarquerez que le seul moyen de récupérer ces dépendances n’utilise pas l’dependency injection mais le modèle de localisateur de service, qui est à l’opposé de l’ inversion de contrôle facilitée par DI .

Simple Injector, l’un des plus gros conteneurs d’dependency injections, explique l’absence de dépendances nommées comme celle-ci :

La résolution d’instances à l’aide d’une clé est une fonctionnalité délibérément laissée de Simple Injector, car elle conduit invariablement à une conception dans laquelle l’application tend à avoir de nombreuses dépendances sur le conteneur DI lui-même. Pour résoudre une instance à clé, vous devrez probablement appeler directement l’instance de conteneur , ce qui conduit à l’ anti-modèle Service Locator .

Cela ne signifie pas que la résolution d’instances à l’aide d’une clé n’est jamais utile. La résolution d’instances à l’aide d’une clé est normalement un travail pour une usine spécifique plutôt que pour le conteneur . Cette approche simplifie considérablement la conception, vous évite de prendre de nombreuses dépendances sur la bibliothèque DI et active de nombreux scénarios que les auteurs de conteneurs DI n’ont tout simplement pas pris en compte.


Cela dit, vous voulez parfois quelque chose de ce genre et il est tout simplement impossible d’avoir un grand nombre de sous-types et d’enregistrements séparés. Dans ce cas, il existe cependant des moyens appropriés pour aborder cette question.

Il existe une situation particulière à laquelle ASP.NET Core peut ressembler dans son code d’infrastructure: Options de configuration nommées pour l’infrastructure d’authentification. Permettez-moi d’essayer d’expliquer rapidement le concept (avec moi):

La stack d’authentification dans ASP.NET Core prend en charge l’enregistrement de plusieurs fournisseurs d’authentification du même type. Par exemple, vous pouvez vous retrouver avec plusieurs fournisseurs OpenID Connect que votre application peut utiliser. Cependant, bien qu’ils partagent tous la même mise en œuvre technique du protocole, il doit exister un moyen de travailler de manière indépendante et de configurer les instances individuellement.

Ce problème est résolu en atsortingbuant à chaque «schéma d’authentification» un nom unique. Lorsque vous ajoutez un schéma, vous enregistrez un nouveau nom et indiquez à l’enregistrement quel type de gestionnaire il doit utiliser. En outre, vous configurez chaque schéma à l’aide de IConfigureNamedOptions qui, lors de son implémentation, reçoit essentiellement un object d’options non configuré qui est ensuite configuré, si son nom correspond. Ainsi, pour chaque type d’authentification T , il y aura éventuellement plusieurs inscriptions pour IConfigureNamedOptions pouvant configurer un object d’options individuel pour un schéma.

À un moment donné, un gestionnaire d’authentification pour un schéma spécifique s’exécute et a besoin de l’object d’options réellement configuré. Pour cela, cela dépend de IOptionsFactory laquelle implémentation par défaut vous permet de créer un object d’options concret qui est ensuite configuré par tous ces IConfigureNamedOptions .

Et cette logique exacte de la fabrique d’options est ce que vous pouvez utiliser pour obtenir une sorte de «dépendance nommée». Traduit dans votre exemple particulier, cela pourrait par exemple ressembler à ceci:

 // container type to hold the client and give it a name public class NamedHttpClient { public ssortingng Name { get; private set; } public HttpClient Client { get; private set; } public NamedHttpClient (ssortingng name, HttpClient client) { Name = name; Client = client; } } // factory to resortingeve the named clients public class HttpClientFactory { private readonly IDictionary _clients; public HttpClientFactory(IEnumerable clients) { _clients = clients.ToDictionary(n => n.Key, n => n.Value); } public HttpClient GetClient(ssortingng name) { if (_clients.TryGet(name, out var client)) return client; // handle error throw new ArgumentException(nameof(name)); } } // register those named clients services.AddSingleton(new NamedHttpClient("A", httpClientA)); services.AddSingleton(new NamedHttpClient("B", httpClientB)); 

Vous devez ensuite injecter HttpClientFactory quelque part et utiliser sa méthode GetClient pour extraire un client nommé.

Évidemment, si vous pensez à cette implémentation et à ce que j’ai écrit plus tôt, cela ressemblera beaucoup à un modèle de localisateur de service. Et d’une certaine manière, c’est vraiment un cas dans ce cas, bien que construit sur le conteneur d’dependency injection existant. Est-ce que cela le rend meilleur? Probablement pas, mais c’est un moyen de mettre en œuvre votre exigence avec le conteneur existant, c’est donc ce qui compte. Pour la défense complète, dans le cas des options d’authentification ci-dessus, la fabrique d’options est une fabrique réelle . Elle construit donc des objects réels et n’utilise pas d’instances pré-enregistrées existantes. Il ne s’agit donc techniquement pas d’ un modèle d’emplacement de service.


De toute évidence, l’autre solution consiste à ignorer complètement ce que j’ai écrit ci-dessus et à utiliser un conteneur d’dependency injection différent avec ASP.NET Core. Par exemple, Autofac prend en charge les dépendances nommées et peut facilement remplacer le conteneur par défaut pour ASP.NET Core .

Utiliser les inscriptions nommées

C’est exactement ce à quoi servent les enregistrements nommés .

Inscrivez-vous comme ceci:

 container.RegisterInstance(new HttpClient(), "ClientA"); container.RegisterInstance(new HttpClient(), "ClientB"); 

Et récupérer de cette façon:

 var clientA = container.Resolve("ClientA"); var clientB = container.Resolve("ClientB"); 

Si vous souhaitez que ClientA ou ClientB soit automatiquement injecté dans un autre type enregistré, consultez cette question . Exemple:

 container.RegisterType( new InjectionConstructor( // Explicitly specify a constructor new ResolvedParameter("ClientA") // Resolve parameter of type HttpClient using name "ClientA" ) ); container.RegisterType( new InjectionConstructor( // Explicitly specify a constructor new ResolvedParameter("ClientB") // Resolve parameter of type HttpClient using name "ClientB" ) ); 

Utiliser une usine

Si votre conteneur IoC ne dispose d’aucune capacité pour gérer les enregistrements nommés, vous pouvez injecter une fabrique et laisser le contrôleur décider de la manière d’obtenir l’instance. Voici un exemple très simple:

 class HttpClientFactory : IHttpClientFactory { private readonly Dictionary _clients; public void Register(ssortingng name, HttpClient client) { _clients[name] = client; } public HttpClient Resolve(ssortingng name) { return _clients[name]; } } 

Et dans vos contrôleurs:

 class ControllerA { private readonly HttpClient _httpClient; public ControllerA(IHttpClientFactory factory) { _httpClient = factory.Resolve("ClientA"); } } 

Et dans votre composition racine:

 var factory = new HttpClientFactory(); factory.Register("ClientA", new HttpClient()); factory.Register("ClientB", new HttpClient()); container.AddSingleton(factory); 

En réalité, le consommateur du service ne devrait pas se soucier de l’implémentation de l’instance qu’il utilise. Dans votre cas, je ne vois aucune raison d’enregistrer manuellement de nombreuses instances différentes de HttpClient . Vous pouvez enregistrer le type une fois et toute instance consommasortingce ayant besoin d’une instance obtiendra sa propre instance de HttpClient . Vous pouvez le faire avec AddTransient .

La méthode AddTransient est utilisée pour mapper des types abstraits à des services concrets instanciés séparément pour chaque object qui en a besoin

 services.AddTransient();