J'ai des services dérivés de la même interface.
public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { }
public class ServiceC : IService { }
En règle générale, d'autres conteneurs IoC comme Unity
vous permettent d'enregistrer des implémentations concrètes par certains Key
qui les distinguent.
Dans ASP.NET Core, comment enregistrer ces services et les résoudre au moment de l'exécution en fonction d'une clé?
Je ne vois aucune Add
méthode de service qui prend un paramètre key
ou name
, qui serait généralement utilisé pour distinguer l'implémentation concrète.
public void ConfigureServices(IServiceCollection services)
{
// How do I register services of the same interface?
}
public MyController:Controller
{
public void DoSomething(string key)
{
// How do I resolve the service by key?
}
}
Le modèle d'usine est-il la seule option ici?
Update1
J'ai parcouru l'article ici qui montre comment utiliser le modèle d'usine pour obtenir des instances de service lorsque nous avons plusieurs implémentations concrètes. Cependant, ce n'est pas encore une solution complète. Quand j'appelle le_serviceProvider.GetService()
méthode, je ne peux pas injecter de données dans le constructeur.
Par exemple, considérez ceci:
public class ServiceA : IService
{
private string _efConnectionString;
ServiceA(string efconnectionString)
{
_efConnecttionString = efConnectionString;
}
}
public class ServiceB : IService
{
private string _mongoConnectionString;
public ServiceB(string mongoConnectionString)
{
_mongoConnectionString = mongoConnectionString;
}
}
public class ServiceC : IService
{
private string _someOtherConnectionString
public ServiceC(string someOtherConnectionString)
{
_someOtherConnectionString = someOtherConnectionString;
}
}
Comment peut-on _serviceProvider.GetService()
injecter la chaîne de connexion appropriée? Dans Unity ou dans toute autre bibliothèque IoC, nous pouvons le faire lors de l'enregistrement du type. Je peux utiliser IOption , cependant, cela me demandera d'injecter tous les paramètres. Je ne peux pas injecter une chaîne de connexion particulière dans le service.
Notez également que j'essaie d'éviter d'utiliser d'autres conteneurs (y compris Unity) car je dois alors enregistrer tout le reste (par exemple, les contrôleurs) avec le nouveau conteneur également.
En outre, l'utilisation du modèle d'usine pour créer des instances de service est contraire à DIP, car elle augmente le nombre de dépendances qu'un client a des détails ici .
Donc, je pense que la DI par défaut dans ASP.NET Core manque deux choses:
- La possibilité d'enregistrer des instances à l'aide d'une clé
- La possibilité d'injecter des données statiques dans les constructeurs lors de l'enregistrement
Update1
être déplacé vers une question différente car l'injection de choses dans les constructeurs est très différente de l'élaboration de l'objet à construireRéponses:
J'ai fait une solution de contournement simple en utilisant
Func
quand je me suis retrouvé dans cette situation.Déclarez d'abord un délégué partagé:
Ensuite, dans votre
Startup.cs
, configurez les multiples enregistrements concrets et une cartographie manuelle de ces types:Et utilisez-le dans n'importe quelle classe enregistrée auprès de DI:
Gardez à l'esprit que dans cet exemple, la clé de la résolution est une chaîne, par souci de simplicité et parce que OP demandait ce cas en particulier.
Mais vous pouvez utiliser n'importe quel type de résolution personnalisé comme clé, car vous ne voulez généralement pas un énorme commutateur n-case pourrir votre code. Cela dépend de la façon dont votre application évolue.
la source
Une autre option consiste à utiliser la méthode d'extension
GetServices
deMicrosoft.Extensions.DependencyInjection
.Enregistrez vos services en tant que:
Puis résolvez avec un peu de Linq:
ou
(en admettant que
IService
possède une propriété de chaîne appelée "Nom")Assurez-vous d'avoir
using Microsoft.Extensions.DependencyInjection;
Mettre à jour
Source AspNet 2.1:
GetServices
la source
IEnumerable<IService>
Il n'est pas pris en charge par
Microsoft.Extensions.DependencyInjection
.Mais vous pouvez brancher un autre mécanisme d'injection de dépendance, comme
StructureMap
voir sa page d'accueil et son projet GitHub .Ce n'est pas difficile du tout:
Ajoutez une dépendance à StructureMap dans votre
project.json
:Injectez-le dans le pipeline ASP.NET à l'intérieur
ConfigureServices
et enregistrez vos classes (voir la documentation)Ensuite, pour obtenir une instance nommée, vous devrez demander le
IContainer
C'est tout.
Pour l'exemple à construire, vous avez besoin
la source
Vous avez raison, le conteneur ASP.NET Core intégré n'a pas le concept d'enregistrer plusieurs services et d'en récupérer un spécifique, comme vous le suggérez, une usine est la seule vraie solution dans ce cas.
Alternativement, vous pouvez passer à un conteneur tiers comme Unity ou StructureMap qui fournit la solution dont vous avez besoin (documentée ici: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing- le conteneur de services par défaut ).
la source
J'ai rencontré le même problème et je veux partager comment je l'ai résolu et pourquoi.
Comme vous l'avez mentionné, il y a deux problèmes. La première:
Alors, quelles options avons-nous? Les gens en suggèrent deux:
Utilisez une usine personnalisée (comme
_myFactory.GetServiceByKey(key)
)Utilisez un autre moteur DI (comme
_unityContainer.Resolve<IService>(key)
)En fait, les deux options sont des usines car chaque conteneur IoC est également une usine (très configurable et compliqué cependant). Et il me semble que d'autres options sont également des variations du modèle Factory.
Alors quelle option est la meilleure alors? Ici, je suis d'accord avec @Sock qui a suggéré d'utiliser une usine personnalisée, et c'est pourquoi.
Tout d'abord, j'essaie toujours d'éviter d'ajouter de nouvelles dépendances quand elles ne sont pas vraiment nécessaires. Je suis donc d'accord avec vous sur ce point. De plus, l'utilisation de deux infrastructures DI est pire que la création d'une abstraction d'usine personnalisée. Dans le second cas, vous devez ajouter une nouvelle dépendance de package (comme Unity), mais dépendre d'une nouvelle interface d'usine est moins mal ici. L'idée principale d'ASP.NET Core DI, je crois, est la simplicité. Il maintient un ensemble minimal de fonctionnalités suivant le principe KISS . Si vous avez besoin de fonctionnalités supplémentaires, bricolage ou utilisez un plongeur correspondant qui implémente la fonctionnalité souhaitée (principe fermé ouvert).
Deuxièmement, nous devons souvent injecter de nombreuses dépendances nommées pour un service unique. Dans le cas d'Unity, vous devrez peut-être spécifier des noms pour les paramètres du constructeur (en utilisant
InjectionConstructor
). Cet enregistrement utilise la réflexion et une logique intelligente pour deviner les arguments du constructeur. Cela peut également entraîner des erreurs d'exécution si l'enregistrement ne correspond pas aux arguments du constructeur. D'un autre côté, lorsque vous utilisez votre propre usine, vous avez un contrôle total sur la façon de fournir les paramètres du constructeur. Il est plus lisible et résolu au moment de la compilation. Principe KISS nouveau.Le deuxième problème:
Tout d'abord, je suis d'accord avec vous que dépendre de nouvelles choses comme
IOptions
(et donc du packageMicrosoft.Extensions.Options.ConfigurationExtensions
) n'est pas une bonne idée. J'ai vu des discussions sur lesIOptions
différents points de vue sur ses avantages. Encore une fois, j'essaie d'éviter d'ajouter de nouvelles dépendances quand elles ne sont pas vraiment nécessaires. Est-ce vraiment nécessaire? Je pense que non. Sinon, chaque implémentation devrait en dépendre sans aucun besoin clair provenant de cette implémentation (pour moi, cela ressemble à une violation du FAI, où je suis également d'accord avec vous). Cela est également vrai en fonction de l'usine, mais dans ce cas, cela peut être évité.L'ASP.NET Core DI fournit une très belle surcharge à cet effet:
la source
IServiceA
dans votre cas. Puisque vous n'utilisez que des méthodes deIService
, vous ne devez avoir de dépendanceIService
que pour .services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));
pour la première classe etservices.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));
pour la seconde.J'injecte simplement un IEnumerable
ConfigureServices dans Startup.cs
Dossier Services
MyController.cs
Extensions.cs
la source
La plupart des réponses ici violent le principe de responsabilité unique (une classe de service ne doit pas résoudre les dépendances elle-même) et / ou utilisent l'anti-modèle de localisateur de service.
Une autre option pour éviter ces problèmes consiste à:
J'ai écrit un article avec plus de détails: Injection de dépendances dans .NET: un moyen de contourner les enregistrements nommés manquants
la source
Un peu tard pour cette soirée, mais voici ma solution: ...
Startup.cs ou Program.cs si gestionnaire générique ...
IMyInterface de configuration de l'interface T
Implémentations concrètes de IMyInterface de T
J'espère que s'il y a un problème à le faire de cette façon, quelqu'un voudra bien montrer pourquoi ce n'est pas la bonne façon de procéder.
la source
IMyInterface<CustomerSavedConsumer>
etIMyInterface<ManagerSavedConsumer>
sont des types de services différents - cela ne répond pas du tout à la question des PO.Apparemment, vous pouvez simplement injecter IEnumerable de votre interface de service! Trouvez ensuite l'instance que vous souhaitez utiliser avec LINQ.
Mon exemple est pour le service AWS SNS mais vous pouvez vraiment faire la même chose pour n'importe quel service injecté.
Commencez
SNSConfig
appsettings.json
Usine SNS
Vous pouvez maintenant obtenir le service SNS pour la région que vous souhaitez dans votre service ou contrôleur personnalisé
la source
Une approche d'usine est certainement viable. Une autre approche consiste à utiliser l'héritage pour créer des interfaces individuelles qui héritent de IService, implémenter les interfaces héritées dans vos implémentations IService et enregistrer les interfaces héritées plutôt que la base. Que l'ajout d'une hiérarchie d'héritage ou d'usines soit le «bon» modèle dépend de qui vous parlez. Je dois souvent utiliser ce modèle lorsque je traite avec plusieurs fournisseurs de bases de données dans la même application qui utilise un générique, tel que
IRepository<T>
, comme fondement de l'accès aux données.Exemples d'interfaces et d'implémentations:
Récipient:
la source
Nécromancement.
Je pense que les gens ici réinventent la roue - et mal, si je puis dire ...
Si vous voulez enregistrer un composant par clé, utilisez simplement un dictionnaire:
Et puis enregistrez le dictionnaire avec la collection de services:
si vous ne souhaitez pas ensuite obtenir le dictionnaire et y accéder par clé, vous pouvez masquer le dictionnaire en ajoutant une méthode de recherche de clé supplémentaire à la collection de services:
(l'utilisation de délégué / fermeture devrait donner à un responsable potentiel une chance de comprendre ce qui se passe - la flèche est un peu cryptique)
Vous pouvez maintenant accéder à vos types avec
ou
Comme nous pouvons le voir, le premier est complètement superflu, car vous pouvez également faire exactement cela avec un dictionnaire, sans nécessiter de fermetures et AddTransient (et si vous utilisez VB, même les accolades ne seront pas différentes):
(plus simple est préférable - vous pouvez cependant l'utiliser comme méthode d'extension)
Bien sûr, si vous n'aimez pas le dictionnaire, vous pouvez également équiper votre interface d'une propriété
Name
(ou autre), et le rechercher par clé:Mais cela nécessite de changer votre interface pour tenir compte de la propriété, et parcourir de nombreux éléments devrait être beaucoup plus lent qu'une recherche de tableau associatif (dictionnaire).
C'est bien de savoir que cela peut être fait sans dicionary, cependant.
Ce ne sont que mes 0,05 $
la source
IDispose
mis en œuvre, qui est responsable de l'élimination du service? Vous avez enregistré le dictionnaire en tant queSingleton
depuis mon post ci-dessus, je suis passé à une classe Factory générique
Usage
la mise en oeuvre
Extension
la source
Bien qu'il semble que @Miguel A. Arilla l'ait clairement indiqué et que j'ai voté pour lui, j'ai créé au-dessus de sa solution utile une autre solution qui semble soignée mais nécessite beaucoup plus de travail.
Cela dépend certainement de la solution ci-dessus. Donc, fondamentalement, j'ai créé quelque chose de similaire
Func<string, IService>>
et je l'ai appelé enIServiceAccessor
tant qu'interface, puis j'ai dû ajouter quelques extensions supplémentaires enIServiceCollection
tant que telles:L'accesseur de service ressemble à:
Le résultat final, vous pourrez enregistrer des services avec des noms ou des instances nommées comme nous le faisions avec d'autres conteneurs ... par exemple:
C'est suffisant pour l'instant, mais pour compléter votre travail, il est préférable d'ajouter plus de méthodes d'extension que vous pouvez pour couvrir tous les types d'inscriptions en suivant la même approche.
Il y avait un autre post sur stackoverflow, mais je ne le trouve pas, où l'affiche a expliqué en détail pourquoi cette fonctionnalité n'est pas prise en charge et comment la contourner, essentiellement similaire à ce que @Miguel a déclaré. C'était un bon article même si je ne suis pas d'accord avec chaque point car je pense qu'il y a des situations où vous avez vraiment besoin d'instances nommées. Je posterai ce lien ici une fois que je le retrouverai.
En fait, vous n'avez pas besoin de passer ce sélecteur ou cet accessoire:
J'utilise le code suivant dans mon projet et cela a bien fonctionné jusqu'à présent.
la source
J'ai créé une bibliothèque pour cela qui implémente de belles fonctionnalités. Le code peut être trouvé sur GitHub: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/
L'utilisation est simple:
Dans l'exemple ci-dessus, notez que pour chaque enregistrement nommé, vous spécifiez également la durée de vie ou Singleton, Scoped ou Transient.
Vous pouvez résoudre les services de deux manières, selon que vous êtes à l'aise avec le fait que vos services dépendent de ce package de non:
ou
J'ai spécifiquement conçu cette bibliothèque pour bien fonctionner avec Microsoft.Extensions.DependencyInjection - par exemple:
Lorsque vous enregistrez des services nommés, tous les types que vous enregistrez peuvent avoir des constructeurs avec des paramètres - ils seront satisfaits via DI, de la même manière que
AddTransient<>
,AddScoped<>
et lesAddSingleton<>
méthodes fonctionnent normalement.Pour les services nommés transitoires et étendus, le registre crée un
ObjectFactory
afin qu'il puisse activer de nouvelles instances du type très rapidement en cas de besoin. Ceci est beaucoup plus rapide que les autres approches et correspond à la façon dont Microsoft.Extensions.DependencyInjection fait les choses.la source
Ma solution pour ce que ça vaut ... a envisagé de passer à Castle Windsor car je ne peux pas dire que j'ai aimé l'une des solutions ci-dessus. Désolé!!
Créez vos différentes implémentations
enregistrement
Utilisation du constructeur et de l'instance ...
la source
Extension de la solution de @rnrneverdies. Au lieu de ToString (), les options suivantes peuvent également être utilisées: 1) Avec une implémentation de propriété commune, 2) Un service de services proposé par @Craig Brunetti.
la source
Après avoir lu les réponses ici et des articles ailleurs, j'ai pu le faire fonctionner sans chaînes. Lorsque vous avez plusieurs implémentations de la même interface, la DI les ajoutera à une collection, il est donc possible de récupérer la version souhaitée de la collection à l'aide
typeof
.la source
var serviceA = new ServiceA();
Bien que l'implémentation prête à l'emploi ne le propose pas, voici un exemple de projet qui vous permet d'enregistrer des instances nommées, puis d'injecter INamedServiceFactory dans votre code et d'extraire des instances par nom. Contrairement aux autres solutions facory ici, cela vous permettra d'enregistrer plusieurs instances de la même implémentation mais configurées différemment
https://github.com/macsux/DotNetDINamedInstances
la source
Que diriez-vous d'un service pour des services?
Si nous avions une interface INamedService (avec la propriété .Name), nous pourrions écrire une extension IServiceCollection pour .GetService (nom de chaîne), où l'extension prendrait ce paramètre de chaîne et ferait un .GetServices () sur lui-même et dans chacun d'eux retourné instance, recherchez l'instance dont INamedService.Name correspond au nom donné.
Comme ça:
Par conséquent, votre IMyService doit implémenter INamedService, mais vous obtiendrez la résolution basée sur les clés que vous souhaitez, non?
Pour être honnête, devoir même avoir cette interface INamedService semble moche, mais si vous vouliez aller plus loin et rendre les choses plus élégantes, alors un [NamedServiceAttribute ("A")] sur l'implémentation / classe pourrait être trouvé par le code dans ce extension, et cela fonctionnerait tout aussi bien. Pour être encore plus juste, la réflexion est lente, donc une optimisation peut être nécessaire, mais honnêtement, c'est quelque chose que le moteur DI aurait dû aider. La rapidité et la simplicité sont chacune de grands contributeurs au TCO.
Dans l'ensemble, il n'y a pas besoin d'une usine explicite, car «trouver un service nommé» est un concept tellement réutilisable, et les classes d'usine ne sont pas évolutives comme solution. Et un Func <> semble bien, mais un bloc de commutateurs est tellement bluffant , et encore une fois, vous écrirez des Funcs aussi souvent que vous écrivez des usines. Commencez simple, réutilisable, avec moins de code, et si cela s'avère ne pas le faire pour vous, alors devenez complexe.
la source
J'ai rencontré le même problème et j'ai travaillé avec une simple extension pour autoriser les services nommés. Vous pouvez le trouver ici:
Il vous permet d'ajouter autant de services (nommés) que vous le souhaitez comme ceci:
La bibliothèque vous permet également d'implémenter facilement un "modèle d'usine" comme ceci:
J'espère que ça aide
la source
J'ai créé ma propre extension par rapport à l' extension
IServiceCollection
utiliséeWithName
:ServiceCollectionTypeMapper
est une instance singleton que les cartesIService
>NameOfService
>Implementation
où une interface pourrait avoir de nombreuses implémentations avec des noms différents, ce qui permet d'enregistrer les types que nous pouvons résoudre quand wee besoin et est une approche différente de résoudre de multiples services pour choisir ce que nous voulons.Pour enregistrer un nouveau service:
Pour résoudre le service, nous avons besoin d'une extension
IServiceProvider
comme celle-ci.Lorsque résoudre:
N'oubliez pas que serviceProvider peut être injecté dans un constructeur dans notre application en tant que
IServiceProvider
.J'espère que ça aide.
la source