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 tels que Unity
vous permettent d'enregistrer des implémentations concrètes avec une Key
qui les distingue.
Dans Asp.Net Core, comment puis-je enregistrer ces services et les résoudre au moment de l'exécution en fonction d'une clé?
Je ne vois aucune des méthodes du service Add
prendre le paramètre key
ou name
qui est généralement utilisé pour distinguer l'implémentation concrète.
public void ConfigureServices(IServiceCollection services)
{
// How do I register services here of the same interface
}
public MyController:Controller
{
public void DoSomeThing(string key)
{
// How do get service based on key
}
}
Le modèle Factory est-il la seule option ici?
Update1
J'ai consulté l'article ici qui montre comment utiliser le modèle de fabrique pour obtenir des instances de service lorsque nous avons plusieurs implémentations concrètes. Cependant, ce n'est toujours pas une solution complète. Lorsque j'appelle la méthode _serviceProvider.GetService()
, je ne peux pas injecter de données dans le constructeur. Par exemple considérons cet exemple
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 _serviceProvider.GetService()
peut-il injecter la chaîne de connexion appropriée? Dans Unity ou tout autre IOC, nous pouvons le faire au moment de l'enregistrement du type. Je peux utiliser IOption mais 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.
De plus, l'utilisation d'un modèle d'usine pour créer une instance de service va à l'encontre de DIP, car l'usine augmente le nombre de dépendances sur lesquelles un client est obligé de dépendre/ détails ici
Donc, je pense que le DI par défaut dans le noyau ASP.NET manque 2 choses
1> Enregistrer les instances avec la clé
2> Injecter des données statiques dans le constructeur lors de l'enregistrement
J'ai fait une solution de contournement simple en utilisant Func
lorsque je me suis retrouvé dans cette situation.
services.AddTransient<Consumer>();
services.AddTransient<ServiceA>();
services.AddTransient<ServiceB>();
services.AddTransient<ServiceC>();
services.AddTransient<Func<string, IService>>(serviceProvider => key =>
{
switch(key)
{
case "A":
return serviceProvider.GetService<ServiceA>();
case "B":
return serviceProvider.GetService<ServiceB>();
case "C":
return serviceProvider.GetService<ServiceC>();
default:
throw new KeyNotFoundException(); // or maybe return null, up to you
}
});
Et utilisez-le dans n’importe quelle classe inscrite auprès de DI comme:
public class Consumer
{
private readonly Func<string, IService> _serviceAccessor;
public Consumer(Func<string, IService> serviceAccessor)
{
_serviceAccessor = serviceAccesor;
}
public void UseServiceA()
{
//use _serviceAccessor field to resolve desired type
_serviceAccessor("A").DoIServiceOperation();
}
}
METTRE &AGRAVE; JOUR
Gardez à l'esprit que, dans cet exemple, la clé de résolution est une chaîne, par souci de simplicité et parce qu'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 commutateur énorme à n cases qui gâte votre code. Cela dépend de la taille de votre application.
Une autre option consiste à utiliser la méthode d'extension GetServices
from Microsoft.Extensions.DependencyInjection
.
Enregistrez vos services en tant que:
services.AddSingleton<IService, ServiceA>();
services.AddSingleton<IService, ServiceB>();
services.AddSingleton<IService, ServiceC>();
Puis résolvez avec un peu de Linq:
var services = serviceProvider.GetServices<IService>();
var serviceB = services.First(o => o.GetType() == typeof(ServiceB));
ou
var serviceZ = services.First(o => o.Name.Equals("Z"));
(en supposant que IService
a une propriété de chaîne appelée "Nom")
Assurez-vous d'avoir using Microsoft.Extensions.DependencyInjection;
Source AspNet 2.1: GetServices
Il n'est pas supporté par Microsoft.Extensions.DependencyInjection
.
Mais vous pouvez brancher un autre mécanisme d'injection de dépendance, tel que StructureMap
Voir sa page d'accueil et son GitHub Project .
Ce n'est pas difficile du tout:
Ajoutez une dépendance à StructureMap dans votre project.json
:
"Structuremap.Microsoft.DependencyInjection" : "1.0.1",
Injectez-le dans le pipeline ASP.NET dans ConfigureServices
et enregistrez vos classes (voir documentation)
public IServiceProvider ConfigureServices(IServiceCollection services) // returns IServiceProvider !
{
// Add framework services.
services.AddMvc();
services.AddWhatever();
//using StructureMap;
var container = new Container();
container.Configure(config =>
{
// Register stuff in container, using the StructureMap APIs...
config.For<IPet>().Add(new Cat("CatA")).Named("A");
config.For<IPet>().Add(new Cat("CatB")).Named("B");
config.For<IPet>().Use("A"); // Optionally set a default
config.Populate(services);
});
return container.GetInstance<IServiceProvider>();
}
Ensuite, pour obtenir une instance nommée, vous devrez demander la variable IContainer
.
public class HomeController : Controller
{
public HomeController(IContainer injectedContainer)
{
var myPet = injectedContainer.GetInstance<IPet>("B");
string name = myPet.Name; // Returns "CatB"
C'est tout.
Pour que l'exemple soit construit, vous avez besoin
public interface IPet
{
string Name { get; set; }
}
public class Cat : IPet
{
public Cat(string name)
{
Name = name;
}
public string Name {get; set; }
}
Vous avez raison, le conteneur ASP.NET Core intégré n'a pas le concept de l'enregistrement de plusieurs services, puis de l'extraction d'un service spécifique. Comme vous le suggérez, une fabrique est la seule solution réelle dans ce cas.
Vous pouvez également basculer vers un conteneur tiers tel que Unity ou StructureMap qui fournit la solution dont vous avez besoin (documenté ici: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?# remplacement-the-default-services-container ).
J'ai fait face au même problème et je veux partager comment j'ai résolu le problème et pourquoi.
Comme vous l'avez mentionné, il y a deux problèmes. La première:
Dans Asp.Net Core, comment puis-je enregistrer ces services et les résoudre à runtime basé sur une clé?
Alors, quelles options avons-nous? Les gens suggèrent deux:
Utilisez une usine personnalisée (comme _myFactory.GetServiceByKey(key)
)
Utilisez un autre moteur DI (comme _unityContainer.Resolve<IService>(key)
)
Le modèle Factory est-il la seule option ici?
En fait, les deux options sont des usines, car chaque conteneur IoC est également une usine (hautement configurable et compliquée). Et il me semble que d'autres options sont également des variantes du motif Factory.
Alors, quelle option est meilleure alors? Ici, je suis d’accord avec @Sock qui a suggéré d’utiliser l’usine personnalisée et c’est pourquoi.
Tout d'abord, j'essaie toujours d'éviter d'ajouter de nouvelles dépendances lorsqu'elles ne sont pas vraiment nécessaires. Je suis donc d'accord avec vous sur ce point. De plus, utiliser deux frameworks DI est pire que de créer une abstraction personnalisée. Dans le second cas, vous devez ajouter une nouvelle dépendance à un paquet (comme Unity), mais dépendre d'une nouvelle interface d'usine est moins maléfique ici. Je crois que l’idée principale d’ASP.NET Core DI est la simplicité. Il maintient un ensemble minimal de fonctionnalités suivant le principe KISS . Si vous avez besoin de fonctionnalités supplémentaires, utilisez le bricolage ou utilisez le Plungin correspondant qui implémente la fonctionnalité désirée (principe ouvert fermé).
Deuxièmement, nous avons souvent besoin d'injecter de nombreuses dépendances nommées pour un seul service. 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'autre part, lorsque vous utilisez votre propre usine, vous avez le contrôle total sur la manière de fournir les paramètres du constructeur. C'est plus lisible et résolu au moment de la compilation. KISS principe encore.
Le deuxième problème:
Comment _serviceProvider.GetService () peut-il injecter la connexion appropriée chaîne?
Premièrement, je conviens avec vous que dépendre de nouvelles choses telles que IOptions
(et donc du paquet Microsoft.Extensions.Options.ConfigurationExtensions
) n’est pas une bonne idée. J'ai vu certains discuter de IOptions
où il y avait des opinions différentes 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. Autrement, chaque implémentation devrait en dépendre, sans aucun besoin clair 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, il est possible d'éviter .
ASP.NET Core DI fournit une surcharge très agréable à cette fin:
var mongoConnection = //...
var efConnection = //...
var otherConnection = //...
services.AddTransient<IMyFactory>(
s => new MyFactoryImpl(
mongoConnection, efConnection, otherConnection,
s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));
Je viens d'injecter simplement un IEnumerable
ConfigureServices dans Startup.cs
Assembly.GetEntryAssembly().GetTypesAssignableFrom<IService>().ForEach((t)=>
{
services.AddScoped(typeof(IService), t);
});
Dossier de services
public interface IService
{
string Name { get; set; }
}
public class ServiceA : IService
{
public string Name { get { return "A"; } }
}
public class ServiceB : IService
{
public string Name { get { return "B"; } }
}
public class ServiceC : IService
{
public string Name { get { return "C"; } }
}
MyController.cs
public class MyController
{
private readonly IEnumerable<IService> _services;
public MyController(IEnumerable<IService> services)
{
_services = services;
}
public void DoSomething()
{
var service = _services.Where(s => s.Name == "A").Single();
}
...
}
Extensions.cs
public static List<Type> GetTypesAssignableFrom<T>(this Assembly assembly)
{
return Assembly.GetTypesAssignableFrom(typeof(T));
}
public static List<Type> GetTypesAssignableFrom(this Assembly assembly, Type compareType)
{
List<Type> ret = new List<Type>();
foreach (var type in Assembly.DefinedTypes)
{
if (compareType.IsAssignableFrom(type) && compareType != type)
{
ret.Add(type);
}
}
return ret;
}
Apparemment, vous pouvez simplement injecter IEnumerable de votre interface de service! Ensuite, recherchez l'instance que vous souhaitez utiliser avec LINQ.
Mon exemple concerne le service AWS SNS, mais vous pouvez en faire de même pour tout service injecté.
Commencez
foreach (string snsRegion in Configuration["SNSRegions"].Split(',', StringSplitOptions.RemoveEmptyEntries))
{
services.AddAWSService<IAmazonSimpleNotificationService>(
string.IsNullOrEmpty(snsRegion) ? null :
new AWSOptions()
{
Region = RegionEndpoint.GetBySystemName(snsRegion)
}
);
}
services.AddSingleton<ISNSFactory, SNSFactory>();
services.Configure<SNSConfig>(Configuration);
SNSConfig
public class SNSConfig
{
public string SNSDefaultRegion { get; set; }
public string SNSSMSRegion { get; set; }
}
appsettings.json
"SNSRegions": "ap-south-1,us-west-2",
"SNSDefaultRegion": "ap-south-1",
"SNSSMSRegion": "us-west-2",
SNS Factory
public class SNSFactory : ISNSFactory
{
private readonly SNSConfig _snsConfig;
private readonly IEnumerable<IAmazonSimpleNotificationService> _snsServices;
public SNSFactory(
IOptions<SNSConfig> snsConfig,
IEnumerable<IAmazonSimpleNotificationService> snsServices
)
{
_snsConfig = snsConfig.Value;
_snsServices = snsServices;
}
public IAmazonSimpleNotificationService ForDefault()
{
return GetSNS(_snsConfig.SNSDefaultRegion);
}
public IAmazonSimpleNotificationService ForSMS()
{
return GetSNS(_snsConfig.SNSSMSRegion);
}
private IAmazonSimpleNotificationService GetSNS(string region)
{
return GetSNS(RegionEndpoint.GetBySystemName(region));
}
private IAmazonSimpleNotificationService GetSNS(RegionEndpoint region)
{
IAmazonSimpleNotificationService service = _snsServices.FirstOrDefault(sns => sns.Config.RegionEndpoint == region);
if (service == null)
{
throw new Exception($"No SNS service registered for region: {region}");
}
return service;
}
}
public interface ISNSFactory
{
IAmazonSimpleNotificationService ForDefault();
IAmazonSimpleNotificationService ForSMS();
}
Maintenant, vous pouvez obtenir le service SNS pour la région que vous souhaitez dans votre service personnalisé ou votre contrôleur.
public class SmsSender : ISmsSender
{
private readonly IAmazonSimpleNotificationService _sns;
public SmsSender(ISNSFactory snsFactory)
{
_sns = snsFactory.ForSMS();
}
.......
}
public class DeviceController : Controller
{
private readonly IAmazonSimpleNotificationService _sns;
public DeviceController(ISNSFactory snsFactory)
{
_sns = snsFactory.ForDefault();
}
.........
}
Bien qu'il semble que @Miguel A. Arilla l'ait clairement souligné et que j'ai voté pour lui, j'ai créé au-dessus de sa solution utile une autre solution qui a l'air bien, mais qui 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é IServiceAccessor
comme interface, puis j'ai dû ajouter quelques extensions supplémentaires à la IServiceCollection
en tant que telle:
public static IServiceCollection AddSingleton<TService, TImplementation, TServiceAccessor>(
this IServiceCollection services,
string instanceName
)
where TService : class
where TImplementation : class, TService
where TServiceAccessor : class, IServiceAccessor<TService>
{
services.AddSingleton<TService, TImplementation>();
services.AddSingleton<TServiceAccessor>();
var provider = services.BuildServiceProvider();
var implementationInstance = provider.GetServices<TService>().Last();
var accessor = provider.GetServices<TServiceAccessor>().First();
var serviceDescriptors = services.Where(d => d.ServiceType == typeof(TServiceAccessor));
while (serviceDescriptors.Any())
{
services.Remove(serviceDescriptors.First());
}
accessor.SetService(implementationInstance, instanceName);
services.AddSingleton<TServiceAccessor>(prvd => accessor);
return services;
}
Le service Accessor ressemble à:
public interface IServiceAccessor<TService>
{
void Register(TService service,string name);
TService Resolve(string name);
}
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:
services.AddSingleton<IEncryptionService, SymmetricEncryptionService, EncyptionServiceAccessor>("Symmetric");
services.AddSingleton<IEncryptionService, AsymmetricEncryptionService, EncyptionServiceAccessor>("Asymmetric");
Cela suffit pour le moment, mais pour compléter votre travail, il est préférable d’ajouter davantage de méthodes d’extension afin de couvrir tous les types d’enregistrements suivant la même approche.
Il y avait un autre article sur stackoverflow, mais je ne le trouve pas, où l'affiche explique 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 billet de Nice 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 accesseur:
J'utilise le code suivant dans mon projet et cela a bien fonctionné jusqu'à présent.
/// <summary>
/// Adds the singleton.
/// </summary>
/// <typeparam name="TService">The type of the t service.</typeparam>
/// <typeparam name="TImplementation">The type of the t implementation.</typeparam>
/// <param name="services">The services.</param>
/// <param name="instanceName">Name of the instance.</param>
/// <returns>IServiceCollection.</returns>
public static IServiceCollection AddSingleton<TService, TImplementation>(
this IServiceCollection services,
string instanceName
)
where TService : class
where TImplementation : class, TService
{
var provider = services.BuildServiceProvider();
var implementationInstance = provider.GetServices<TService>().LastOrDefault();
if (implementationInstance.IsNull())
{
services.AddSingleton<TService, TImplementation>();
provider = services.BuildServiceProvider();
implementationInstance = provider.GetServices<TService>().Single();
}
return services.RegisterInternal(instanceName, provider, implementationInstance);
}
private static IServiceCollection RegisterInternal<TService>(this IServiceCollection services,
string instanceName, ServiceProvider provider, TService implementationInstance)
where TService : class
{
var accessor = provider.GetServices<IServiceAccessor<TService>>().LastOrDefault();
if (accessor.IsNull())
{
services.AddSingleton<ServiceAccessor<TService>>();
provider = services.BuildServiceProvider();
accessor = provider.GetServices<ServiceAccessor<TService>>().Single();
}
else
{
var serviceDescriptors = services.Where(d => d.ServiceType == typeof(IServiceAccessor<TService>));
while (serviceDescriptors.Any())
{
services.Remove(serviceDescriptors.First());
}
}
accessor.Register(implementationInstance, instanceName);
services.AddSingleton<TService>(prvd => implementationInstance);
services.AddSingleton<IServiceAccessor<TService>>(prvd => accessor);
return services;
}
//
// Summary:
// Adds a singleton service of the type specified in TService with an instance specified
// in implementationInstance to the specified Microsoft.Extensions.DependencyInjection.IServiceCollection.
//
// Parameters:
// services:
// The Microsoft.Extensions.DependencyInjection.IServiceCollection to add the service
// to.
// implementationInstance:
// The instance of the service.
// instanceName:
// The name of the instance.
//
// Returns:
// A reference to this instance after the operation has completed.
public static IServiceCollection AddSingleton<TService>(
this IServiceCollection services,
TService implementationInstance,
string instanceName) where TService : class
{
var provider = services.BuildServiceProvider();
return RegisterInternal(services, instanceName, provider, implementationInstance);
}
/// <summary>
/// Registers an interface for a class
/// </summary>
/// <typeparam name="TInterface">The type of the t interface.</typeparam>
/// <param name="services">The services.</param>
/// <returns>IServiceCollection.</returns>
public static IServiceCollection As<TInterface>(this IServiceCollection services)
where TInterface : class
{
var descriptor = services.Where(d => d.ServiceType.GetInterface(typeof(TInterface).Name) != null).FirstOrDefault();
if (descriptor.IsNotNull())
{
var provider = services.BuildServiceProvider();
var implementationInstance = (TInterface)provider?.GetServices(descriptor?.ServiceType)?.Last();
services?.AddSingleton(implementationInstance);
}
return services;
}
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. Pardon!!
public interface IStage<out T> : IStage { }
public interface IStage {
void DoSomething();
}
Créez vos différentes implémentations
public class YourClassA : IStage<YouClassA> {
public void DoSomething()
{
...TODO
}
}
public class YourClassB : IStage<YourClassB> { .....etc. }
Enregistrement
services.AddTransient<IStage<YourClassA>, YourClassA>()
services.AddTransient<IStage<YourClassB>, YourClassB>()
Utilisation du constructeur et de l'instance ...
public class Whatever
{
private IStage ClassA { get; }
public Whatever(IStage<YourClassA> yourClassA)
{
ClassA = yourClassA;
}
public void SomeWhateverMethod()
{
ClassA.DoSomething();
.....
}
La plupart des réponses ici enfreignent le principe de responsabilité unique (une classe de service ne doit pas résoudre les dépendances en soi) 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épendance dans .NET: un moyen de contourner les enregistrements nommés manquants
Nécromancie.
Je pense que les gens ici réinventent la roue - et mal, si je puis me permettre ...
Si vous souhaitez enregistrer un composant par clé, utilisez simplement un dictionnaire:
System.Collections.Generic.Dictionary<string, IConnectionFactory> dict =
new System.Collections.Generic.Dictionary<string, IConnectionFactory>(
System.StringComparer.OrdinalIgnoreCase);
dict.Add("ReadDB", new ConnectionFactory("connectionString1"));
dict.Add("WriteDB", new ConnectionFactory("connectionString2"));
dict.Add("TestDB", new ConnectionFactory("connectionString3"));
dict.Add("Analytics", new ConnectionFactory("connectionString4"));
dict.Add("LogDB", new ConnectionFactory("connectionString5"));
Et puis enregistrez le dictionnaire avec le service-collection:
services.AddSingleton<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(dict);
si vous ne souhaitez pas 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é/de clôture devrait permettre à un responsable de maintenance éventuel de comprendre ce qui se passe - la notation en forme de flèche est un peu cryptique)
services.AddTransient<Func<string, IConnectionFactory>>(
delegate (IServiceProvider sp)
{
return
delegate (string key)
{
System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService
<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(sp);
if (dbs.ContainsKey(key))
return dbs[key];
throw new System.Collections.Generic.KeyNotFoundException(key); // or maybe return null, up to you
};
});
Maintenant, vous pouvez accéder à vos types avec soit
IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<Func<string, IConnectionFactory>>(serviceProvider)("LogDB");
logDB.Connection
ou
System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider);
dbs["logDB"].Connection
Comme nous pouvons le constater, le premier est tout à fait superflu, car vous pouvez également faire exactement cela avec un dictionnaire, sans nécessiter de fermeture ni AddTransient (et si vous utilisez VB, même les accolades ne seront pas différentes):
IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider)["logDB"];
logDB.Connection
(plus simple, mieux c'est - vous voudrez peut-être l'utiliser comme méthode d'extension)
Bien sûr, si vous n'aimez pas le dictionnaire, vous pouvez également équiper votre interface avec une propriété Name
(ou autre) et le rechercher par clé:
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("ReadDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("WriteDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("TestDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("Analytics"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("LogDB"));
// https://stackoverflow.com/questions/39174989/how-to-register-multiple-implementations-of-the-same-interface-in-asp-net-core
services.AddTransient<Func<string, IConnectionFactory>>(
delegate(IServiceProvider sp)
{
return
delegate(string key)
{
System.Collections.Generic.IEnumerable<IConnectionFactory> svs =
sp.GetServices<IConnectionFactory>();
foreach (IConnectionFactory thisService in svs)
{
if (key.Equals(thisService.Name, StringComparison.OrdinalIgnoreCase))
return thisService;
}
return null;
};
});
Mais cela nécessite de modifier votre interface pour l'adapter à la propriété, et la lecture en boucle d'un grand nombre d'éléments devrait être beaucoup plus lente qu'une recherche de tableau associatif (dictionnaire).
Cela fait plaisir de savoir que cela peut se faire sans dictionnaire, cependant.
Ce ne sont que mes 0,05 $
Un peu tard pour cette soirée, mais voici ma solution: ...
Startup.cs ou Program.cs si Generic Handler ...
services.AddTransient<IMyInterface<CustomerSavedConsumer>, CustomerSavedConsumer>();
services.AddTransient<IMyInterface<ManagerSavedConsumer>, ManagerSavedConsumer>();
IMyInterface of T Interface Setup
public interface IMyInterface<T> where T : class, IMyInterface<T>
{
Task Consume();
}
Implémentations concrètes de IMyInterface of T
public class CustomerSavedConsumer: IMyInterface<CustomerSavedConsumer>
{
public async Task Consume();
}
public class ManagerSavedConsumer: IMyInterface<ManagerSavedConsumer>
{
public async Task Consume();
}
Espérons que s'il y a un problème à le faire de cette façon, quelqu'un soulignera gentiment pourquoi c'est une mauvaise façon de procéder.
Une approche d'usine est certainement viable. Une autre approche consiste à utiliser l'héritage pour créer des interfaces individuelles héritant d'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 vous ajoutiez une hiérarchie d'héritage ou des usines est le bon modèle, tout dépend de votre interlocuteur. Je dois souvent utiliser ce modèle lorsque je traite avec plusieurs fournisseurs de base de données dans la même application qui utilise un générique, tel que IRepository<T>
, en tant que fondement de l'accès aux données.
Exemples d'interfaces et d'implémentations:
public interface IService
{
}
public interface IServiceA: IService
{}
public interface IServiceB: IService
{}
public IServiceC: IService
{}
public class ServiceA: IServiceA
{}
public class ServiceB: IServiceB
{}
public class ServiceC: IServiceC
{}
Récipient:
container.Register<IServiceA, ServiceA>();
container.Register<IServiceB, ServiceB>();
container.Register<IServiceC, ServiceC>();
Extension de la solution de @rnrneverdies. Au lieu de ToString (), les options suivantes peuvent également être utilisées: 1) Avec la mise en œuvre de propriétés communes, 2) Un service de services suggéré par @Craig Brunetti.
public interface IService { }
public class ServiceA : IService
{
public override string ToString()
{
return "A";
}
}
public class ServiceB : IService
{
public override string ToString()
{
return "B";
}
}
/// <summary>
/// extension method that compares with ToString value of an object and returns an object if found
/// </summary>
public static class ServiceProviderServiceExtensions
{
public static T GetService<T>(this IServiceProvider provider, string identifier)
{
var services = provider.GetServices<T>();
var service = services.FirstOrDefault(o => o.ToString() == identifier);
return service;
}
}
public void ConfigureServices(IServiceCollection services)
{
//Initials configurations....
services.AddSingleton<IService, ServiceA>();
services.AddSingleton<IService, ServiceB>();
services.AddSingleton<IService, ServiceC>();
var sp = services.BuildServiceProvider();
var a = sp.GetService<IService>("A"); //returns instance of ServiceA
var b = sp.GetService<IService>("B"); //returns instance of ServiceB
//Remaining configurations....
}
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 les instances par nom. Contrairement aux autres solutions proposées ici, cela vous permettra d’enregistrer plusieurs instances de même implémentation mais configurées différemment.
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 chacun d'eux renvoyé instance, recherchez l’instance dont INamedService.Name correspond au nom donné.
Comme ça:
public interface INamedService
{
string Name { get; }
}
public static T GetService<T>(this IServiceProvider provider, string serviceName)
where T : INamedService
{
var candidates = provider.GetServices<T>();
return candidates.FirstOrDefault(s => s.Name == serviceName);
}
Par conséquent, votre IMyService doit implémenter INamedService, mais vous obtiendrez la résolution basée sur la clé que vous souhaitez, non?
Pour être juste, avoir même cette interface INamedService semble moche, mais si vous voulez aller plus loin et rendre les choses plus élégantes, alors un code [NamedServiceAttribute ("A")] sur la mise en oeuvre/classe pourrait être trouvé par le code dans ce fichier. extension, et cela fonctionnerait aussi bien. Pour être encore plus juste, Reflection est lent, donc une optimisation est peut-être nécessaire, mais honnêtement, c’est quelque chose que le moteur de DI aurait dû aider. La rapidité et la simplicité contribuent grandement au TCO.
Dans l’ensemble, il n’est pas nécessaire de recourir à une fabrique explicite, car la "recherche d’un service nommé" est un concept réutilisable, et les classes d’usine ne sont pas une solution. Et un Func <> semble bien, mais un bloc de commutateurs est si bleh , et encore une fois, vous écrivez Funcs aussi souvent que vous écrivez Factories. Commencez simple, réutilisable, avec moins de code, et si cela s'avère ne pas le faire pour vous, passez au complexe.