如何在Asp.Net Core中注册同一个接口的多个实现?

我有从相同的接口派生的服务

public interface IService { } public class ServiceA : IService { } public class ServiceB : IService { } public class ServiceC : IService { } 

通常像Unity这样的其他IOC容器允许你通过一些Key来注册具体的实现,区别它们。

在Asp.Net核心如何注册这些服务,并在运行时根据一些密钥解决它?

我没有看到任何Add服务方法采用通常用于区分具体实现的keyname参数。

  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 } } 

工厂模式是唯一的select吗?

UPDATE1
我已经去了这里的文章,展示了当我们有多个concreate实现时,如何使用工厂模式来获取服务实例。 但是它仍然不是完整的解决scheme。 当我调用_serviceProvider.GetService()方法我不能注入数据到构造函数。 例如考虑这个例子

 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; } } 

_serviceProvider.GetService()如何注入适当的连接string? 在Unity或任何其他IOC中,我们可以在注册时进行此操作。 我可以使用IOption但是这将需要我注入所有的设置,我不能注入一个特定的连接string到服务。

还要注意,我试图避免使用其他容器(包括Unity),因为那么我必须注册一切(例如控制器)与新的容器以及。

同样使用工厂模式来创build服务实例是对付DIP,因为工厂增加了依赖关系的数量客户端被迫依赖细节在这里

所以我认为在ASP.NET核心的默认DI缺less2件事情
1>使用密钥注册实例
2>在注册期间将静态数据注入构造函数

当我遇到这种情况时,我使用Func做了一个简单的解决方法。

 services.AddTransient<ServiceA>(); services.AddTransient<ServiceB>(); services.AddTransient<ServiceC>(); services.AddTransient(factory => { Func<string, IService> accesor = key => { switch(key) { case "A": return factory.GetService<ServiceA>(); case "B": return factory.GetService<ServiceB>(); case "C": return factory.GetService<ServiceC>(); default: throw new KeyNotFoundException(); // or maybe return null, up to you } }; return accesor; }); 

并且使用从DI注册的任何类,如:

 public class Consumer { private readonly Func<string, IService> _serviceAccessor; public Consumer(Func<string, IService> serviceAccessor) { _serviceAccessor = serviceAccesor; } public void UseServiceA() { serviceAccessor("A").DoIServiceOperation(); } } 

它不受Microsoft.Extensions.DependencyInjection支持。

但是你可以插入另一个dependency injection机制,比如StructureMap 查看它的主页 ,它是GitHub项目 。

这并不难:

  1. project.json添加一个依赖到StructureMap:

     "Structuremap.Microsoft.DependencyInjection" : "1.0.1", 
  2. 将其注入到ConfigureServices的ASP.NETpipe道中,并注册您的类(请参阅文档)

     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>(); } 
  3. 然后,要获得一个命名实例,您将需要请求IContainer

     public class HomeController : Controller { public HomeController(IContainer injectedContainer) { var myPet = injectedContainer.GetInstance<IPet>("B"); string name = myPet.Name; // Returns "CatB" 

而已。

对于构build的例子,你需要

  public interface IPet { string Name { get; set; } } public class Cat : IPet { public Cat(string name) { Name = name; } public string Name {get; set; } } 

我面临同样的问题,并希望分享我是如何解决它,为什么。

如你所说,有两个问题。 首先:

在Asp.Net核心如何注册这些服务,并在运行时根据一些密钥解决它?

那么我们有什么select? 人们build议两个:

  • 使用自定义工厂(如_myFactory.GetServiceByKey(key)

  • 使用另一个DI引擎(如_unityContainer.Resolve<IService>(key)

工厂模式是唯一的select吗?

事实上,两个选项都是工厂,因为每个IoC容器也是一个工厂(虽然高度可configuration和复杂)。 在我看来,其他选项也是工厂模式的变种。

那么哪个选项更好? 在这里我同意@Sock谁build议使用自定义工厂,这就是为什么。

首先,我总是尽量避免在不需要的时候增加新的依赖关系。 所以我在这一点上同意你的意见。 而且,使用两个DI框架比创build定制工厂抽象更差。 在第二种情况下,您必须添加新的程序包依赖项(如Unity),但取决于新的工厂界面在这里不太邪恶。 我相信ASP.NET Core DI的主要思想是简单。 它遵循KISS原则保持最小的一组特征。 如果你需要一些额外的function,然后DIY或使用相应的Plungin实现所需的function(开放封闭原则)。

其次,我们经常需要为单个服务注入许多命名的依赖关系。 在Unity的情况下,您可能必须指定构造函数参数的名称(使用InjectionConstructor )。 这个注册使用reflection和一些聪明的逻辑来猜测构造函数的参数。 如果注册与构造函数参数不匹配,这也可能导致运行时错误。 另一方面,当使用自己的工厂时,您可以完全控制如何提供构造函数参数。 它更具可读性,并在编译时解决。 KISS原则 。

第二个问题:

_serviceProvider.GetService()如何注入适当的连接string?

首先,我同意你的看法,根据新的东西,如IOptions (因此在包Microsoft.Extensions.Options.ConfigurationExtensions )不是一个好主意。 我已经看到一些关于IOptions讨论,关于它的IOptions有不同的意见。 再次,我试图避免添加新的依赖关系,当他们不是真的需要。 真的需要吗? 我想不是。 否则,每个实现将不得不依赖于它,而没有任何明确的需求(对我来说,这看起来像是违反了ISP,我也同意你的意见)。 这也取决于工厂,但在这种情况下,这是可以避免的。

ASP.NET Core DI为此目的提供了一个非常好的重载:

 var mongoConnection = //... var efConnection = //... var otherConnection = //... services.AddTransient<IMyFactory>( s => new MyFactoryImpl( mongoConnection, efConnection, otherConnection, s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>()))); 

另一个选项是使用Microsoft.Extensions.DependencyInjection的扩展方法GetServices

注册您的服务为:

 services.AddSingleton<IService, ServiceA>(); services.AddSingleton<IService, ServiceB>(); services.AddSingleton<IService, ServiceC>(); 

然后用一点Linq解决:

 var services = serviceProvider.GetServices<IService>(); var serviceB = services.First(o => o.GetType() == typeof(ServiceB)); 

要么

 var serviceZ = services.First(o => o.Name.Equals("Z")); 

(假设IService有一个名为“Name”的string属性)

确保using Microsoft.Extensions.DependencyInjection;

你是正确的,build立在ASP.NET核心容器没有注册多个服务的概念,然后检索一个特定的,如你所说,一个工厂是唯一真正的解决scheme在这种情况下。

或者,您可以切换到像Unity或StructureMap这样的第三方容器,它提供了您需要的解决scheme(请参阅https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing-默认服务容器; )。

显然,你可以只注入IEnumerable的服务接口! 然后find你想要使用LINQ的实例。

我的例子是AWS SNS服务,但是对于任何注入的服务,你可以做同样的事情。

启动

 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工厂

 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(); } 

现在,您可以在自定义服务或控制器中获取所需区域的SNS服务

 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(); } ......... }