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


239

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

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

通常,其他IoC容器例如 Unity允许您通过一些Key区分它们的具体实现来注册。

在ASP.NET Core中,如何注册这些服务并在运行时基于某些键解析它们?

我看不到任何Add带有keyname参数的Service方法,这些方法通常用于区分具体实现。

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

工厂模式是这里唯一的选择吗?

Update1
看过这里的文章展示了如何使用工厂模式来获得服务实例的时候,我们有多种具体实现。但是,它仍然不是一个完整的解决方案。调用_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()注入适当的连接字符串?在Unity或任何其他IoC库中,我们可以在类型注册时执行此操作。我可以使用IOption,但这将要求我注入所有设置。我无法将特定的连接字符串注入服务。

还要注意,我试图避免使用其他容器(包括Unity),因为那样的话,我还必须向新容器注册其他所有内容(例如Controllers)。

另外,使用工厂模式创建服务实例也违反了DIP,因为它增加了客户端在此处具有详细信息的依赖关系数量

因此,我认为ASP.NET Core中的默认DI缺少两件事:

  1. 使用密钥注册实例的能力
  2. 在注册过程中将静态数据注入构造函数的能力


2
nuget中终于有了一个扩展,用于基于名称的注册,希望它能有所帮助
neleus

嗨,很抱歉,我提出了愚蠢的问题,但是我想了解有关Microsoft.Extensions.DependencyInjection的新知识……您是否认为创建3个扩展Iservice的空接口,例如“ public interface IServiceA:IService”,而不是“ public class ServiceA:IServiceA” “ ...可能是一个很好的选择?
艾米利亚诺·马格里奥卡

这篇文章有用吗?stevejgordon.co.uk/...
迈克乙

可以Update1移至另一个问题,因为在构造函数中注入事物与确定构造哪个对象有很大不同
Neil

Answers:


245

Func当我发现自己处于这种情况时,我做了一个简单的解决方法。

首先声明一个共享委托:

public delegate IService ServiceResolver(string key);

然后在您的中Startup.cs,设置多个具体的注册以及这些类型的手动映射:

services.AddTransient<ServiceA>();
services.AddTransient<ServiceB>();
services.AddTransient<ServiceC>();

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

并从DI注册的任何类中使用它:

public class Consumer
{
    private readonly IService _aService;

    public Consumer(ServiceResolver serviceAccessor)
    {
        _aService = serviceAccessor("A");
    }

    public void UseServiceA()
    {
        _aService.DoTheThing();
    }
}

请记住,在此示例中,为简单起见,并且因为OP特别要求这种情况,所以解析的键是字符串。

但是您可以使用任何自定义解析类型作为键,因为通常不需要大型n-case开关使代码腐烂。取决于您的应用扩展方式。


1
@MatthewStevenMonkan用示例更新了我的答案
Miguel A. Arilla

2
使用这样的工厂模式是最好的方法。感谢分享!
谢尔盖·阿科波夫

2
+1非常干净整洁,因为当我们使用其他di容器时,无论何时我们需要解决依赖关系时,我们都必须包括它们的包。AutoFac中的ILifetimeScope。
阿努帕姆·辛格

1
@AnupamSingh我认为,大多数在.NET Core上运行的中小型应用程序都不需要任何DI框架,只需添加复杂性和不需要的依赖项,内置DI的美观和简单性已绰绰有余,并且可以也可以轻松扩展。
Miguel A. Arilla

7
不赞成投票的解释-非常有趣,但我目前正在重构庞大的代码库,以删除几年前某人(在MS DI革命之前)所做的所有Func魔术操作。问题在于,它极大地增加了属性的连续性复杂性,可能会导致更复杂的DI分辨率下降。例如,我在一个Windows服务处理程序上工作过,其中有1.6k行以上的代码与Func有关,在执行了推荐的DI方法之后,我将其减少至0.2k行。好的行代码没有任何意义..除了它现在更易于阅读和重用...
Piotr Kula

79

另一种选择是使用扩展方法GetServicesMicrosoft.Extensions.DependencyInjection

将您的服务注册为:

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”的字符串属性)

确保有 using Microsoft.Extensions.DependencyInjection;

更新资料

AspNet 2.1的来源: GetServices


6
不确定,但我认为这不是确定性的。您今天获得的任何结果明天可能都会改变,这似乎不是一个好习惯。
rneverdies

4
赞成GetServices的链接,该链接向我显示您可以通过请求来请求依赖服务的服务列表IEnumerable<IService>
johnny

20
serviceProvider.GetServices <IService>()将实例化ServiceA,ServiceB和ServiceC。您只想调用一项服务的构造函数-您实际需要的一项。如果实现不是很轻巧,或者您有很多IService实现(例如,对于每个模型,都有自动生成的IRepository实现),这将是一个大问题。
乌罗斯(Uros)'18

6
我同意@Uros。这不是一个好的解决方案。想象一下,如果您注册10个IService实现,然后实际需要的实例是最后一个实例,将会发生什么。在这种情况下,DI实际创建了9个实例,但从未使用过。
thomai

4
坏主意:多个未使用的实例,服务定位器反模式以及直接耦合到实际实现(typeof <ServiceA>)。
里科·苏特

20

不支持 Microsoft.Extensions.DependencyInjection

但是您可以插入另一种依赖项注入机制,例如“ StructureMap 查看”它的主页和“ GitHub项目”

一点都不难:

  1. 在您的中将依赖项添加到StructureMap中project.json

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

    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"

而已。

为了构建示例,您需要

    public interface IPet
    {
        string Name { get; set; }
    }

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

        public string Name {get; set; }
    }

我尝试过这种方法,但是由于在构建计划中未找到IContainer,因此在控制器上出现运行时错误。有什么需要我自动注入IContainer的内容吗?
mohrtan

顺便说一句,我正在使用StructureMap.Micorosoft.DependencyInjection 1.3.0。
mohrtan

您是否要在ConfigureServices中返回新容器?
Gerardo Grignoli

如上面的步骤2所示,我将返回新容器的IServiceProviderInstance。我只复制了它,仅更改了我的类型。这是一个很好的解决方案,并且运行良好。唯一的缺点是我无法使用注入的容器,而诉诸于静态容器,而我不想这样做。
mohrtan

1
它为我工作,感谢GerardoGrignoli。@mohrtan如果您仍在研究示例代码,请参见此处。github.com/Yawarmurtaza/AspNetCoreStructureMap
Murtaza


13

我曾经遇到过同样的问题,并且想分享我如何解决它以及为什么。

正如您提到的,有两个问题。首先:

在Asp.Net Core中,我该如何注册这些服务并在运行时根据某些键对其进行解析?

那么我们有什么选择呢?人们建议两个:

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

  • 使用其他DI引擎(如_unityContainer.Resolve<IService>(key)

工厂模式是这里唯一的选择吗?

实际上,这两个选项都是工厂,因为每个IoC容器也是工厂(尽管高度可配置和复杂)。在我看来,其他选项也是Factory模式的变体。

那么,哪种选择更好呢?在这里,我同意建议使用自定义工厂的@Sock,这就是原因。

首先,我总是尽量避免在不需要时添加新的依赖项。所以我在这一点上同意你的观点。而且,使用两个DI框架比创建自定义工厂抽象要差。在第二种情况下,您必须添加新的程序包依赖关系(例如Unity),但是在这里依靠新的工厂接口的危害就小了。我相信,ASP.NET Core DI的主要思想是简单性。它遵循KISS原理,保留了最少的功能集。如果您需要一些额外的功能,则DIY或使用相应的Plungin实现所需功能(开放式封闭原理)。

其次,通常我们需要为单个服务注入许多已命名的依赖项。如果是Unity,则可能必须为构造函数参数指定名称(使用InjectionConstructor)。该注册使用反射和一些智能逻辑来猜测构造函数的参数。如果注册与构造函数参数不匹配,这也可能导致运行时错误。另一方面,使用自己的工厂时,您可以完全控制如何提供构造函数参数。它更具可读性,并且在编译时已解决。吻原理再次采用。

第二个问题:

_serviceProvider.GetService()如何注入适当的连接字符串?

首先,我同意你的观点,依靠新事物IOptions(例如,因此依赖于package Microsoft.Extensions.Options.ConfigurationExtensions)不是一个好主意。我看过一些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的新知识……您认为创建3个扩展Iservice的接口,例如“ public interface IServiceA:IService”,而不是“ public class ServiceA:IServiceA” ...可能是一个很好的选择?
Emiliano Magliocca'8

1
@ emiliano-magliocca通常,在这种IServiceA情况下,您不应该依赖不使用的接口(ISP)。由于您IService仅使用from的方法,因此您应该具有IServiceonly的依赖关系。
neleus

1
@ cagatay-kalan如果遇到OP的问题,他可以使用ASP.NET Core DI轻松实现其目标。无需其他DI框架。
neleus

1
@EmilianoMagliocca这样可以很容易地解决:services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));对于第一堂课和services.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));第二堂课。
neleus

1
@EmilianoMagliocca在我的示例中,“ MyFirstClass”和“ MySecondClass”都具有与Escpos和Usbpos都实现的接口类型相同的ctor参数。因此,上面的代码仅指示IoC容器如何实例化“ MyFirstClass”和“ MySecondClass”。而已。因此,此外,您可能需要将其他一些接口映射到“ MyFirstClass”和“ MySecondClass”。这取决于您的需求,在我的示例中并未涉及。
neleus

13

我只是简单地注入一个IEnumerable

Startup.cs中的ConfigureServices

Assembly.GetEntryAssembly().GetTypesAssignableFrom<IService>().ForEach((t)=>
                {
                    services.AddScoped(typeof(IService), t);
                });

服务文件夹

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

在Controller的DoSomething()方法中,可以使用typeof解析所需的服务:var service = _services.FirstOrDefault(t => t.GetType()== typeof(ServiceA));
Ciaran Bruen

我确实尝试了一切,这是唯一对我有用的解决方案。谢谢!
Skatz1990

@ Skatz1990在另一篇文章中尝试我在下面创建的解决方案。我认为它更干净,更简单。
T布朗

12

这里的大多数答案都违反了单一责任原则(服务类不应自行解决依赖关系)和/或使用服务定位器反模式。

避免这些问题的另一种选择是:

  • 在接口上使用附加的通用类型参数,或者在实现非通用接口的新接口上使用,
  • 实现适配器/拦截器类以添加标记类型,然后
  • 使用通用类型作为“名称”

我写了一篇更详细的文章:.NET中的依赖注入:一种解决缺少命名注册的方法


被接受者如何回答紫罗兰的单一责任原则?
LP13

请参阅stackoverflow.com/a/52066039/876814的注释,也可以在接受的答案中懒惰地解决服务,即,您仅知道服务是否在运行时失败,并且无法在容器构建后的启动时进行静态检查(类似于评论中的答案)。SRP,因为该服务不仅负责其业务逻辑,还负责依赖性解决方案
Rico Suter

@RicoSuter我真的很喜欢您博客中的解决方案,但是您对Startup类中的DI感到困惑。具体来说,我看不到MessagePublisher(“ MyOrderCreatedQueue”)行,因为我看不到带有该签名的构造函数。services.AddSingleton <IMessagePublisher <OrderCreatedMessage >>(new MessagePublisher <OrderCreatedMessage>(new MessagePublisher(“ MyOrderCreatedQueue”))));
李Z

谢谢,更新了本文,并将MyMessagePublisher用作IMessagePublisher的示例实现
Rico Suter

7

参加这个聚会有点晚,但是这是我的解决方案:...

如果是通用处理程序,则为Startup.cs或Program.cs。

services.AddTransient<IMyInterface<CustomerSavedConsumer>, CustomerSavedConsumer>();
services.AddTransient<IMyInterface<ManagerSavedConsumer>, ManagerSavedConsumer>();

T接口设置的IMyInterface

public interface IMyInterface<T> where T : class, IMyInterface<T>
{
    Task Consume();
}

T的IMyInterface的具体实现

public class CustomerSavedConsumer: IMyInterface<CustomerSavedConsumer>
{
    public async Task Consume();
}

public class ManagerSavedConsumer: IMyInterface<ManagerSavedConsumer>
{
    public async Task Consume();
}

希望这样做是否有任何问题,有人会指出为什么这样做是错误的。


2
IMyInterface<CustomerSavedConsumer>并且IMyInterface<ManagerSavedConsumer>不同的服务类型-这根本无法回答OP的问题。
理查德·豪尔

2
OP需要一种在Asp.net核心中注册同一接口的多个实现的方法。如果我没有这样做,请(完全)说明。
灰色,

1
正确无误时,此模式可以达到op想要的效果。至少当我自己尝试执行此操作时,我偶然发现了这篇文章,并且我的解决方案最适合我的情况。
灰色

1
我希望问题更多在于,为一个接口注册多个实现(使用MS DI)不允许容器将一个实现与另一个实现区分开。在其他DI中,您可以键入它们的密码,以便容器知道选择哪个。在MS中,您必须使用委托并手动选择。您的解决方案无法解决这种情况,因为您的界面不同,因此容器选择正确的实现没有问题。虽然您的示例显然可以正常工作,但它不能解决上述问题。
理查德·豪尔

3
@Gray即使您的帖子受到了负面的报道,我仍然感谢您提出此解决方案。它为读者提供了克服.net核心DI中限制的另一个选择。尽管它可能无法直接回答OP问题,但它提供了一个完美的替代解决方案,这就是SO的全部含义,对吗?
尼尔·沃森

5

显然,您只需注入IEnumerable服务接口即可!然后找到您要使用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();
    }

     .........
}

5

工厂方法当然是可行的。另一种方法是使用继承来创建从IService继承的单个接口,在IService实现中实现继承的接口,并注册继承的接口而不是基础接口。添加继承层次结构还是工厂是“正确的”模式,这完全取决于您与谁交谈。在同一个应用程序中使用泛型(例如,IRepository<T>数据访问基础的。

示例接口和实现:

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

容器:

container.Register<IServiceA, ServiceA>();
container.Register<IServiceB, ServiceB>();
container.Register<IServiceC, ServiceC>();

5

死灵法师。
我认为这里的人们正在重新发明轮子-糟糕的是,如果我可以这样说的话...
如果您想通过按键注册组件,只需使用字典:

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"));

然后使用service-collection注册字典:

services.AddSingleton<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(dict);

如果您随后不愿意通过字典来获取字典并通过密钥来访问字典,则可以通过向服务集合中添加其他密钥查找方法来隐藏字典:(
使用委托/关闭应该为准维护者提供机会了解发生了什么-箭头符号有点神秘)

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

现在您可以使用以下任一方式访问您的类型

IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<Func<string, IConnectionFactory>>(serviceProvider)("LogDB");
logDB.Connection

要么

System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider);
dbs["logDB"].Connection

如我们所见,第一个完全是多余的,因为您也可以使用字典来完全做到这一点,而无需闭包和AddTransient(并且如果使用VB,甚至括号也不会有所不同):

IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider)["logDB"];
logDB.Connection

(越简单越好-不过您可能希望将其用作扩展方法)

当然,如果您不喜欢字典,也可以为接口配备属性Name(或其他属性),然后按键进行查找:

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"));



// /programming/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;
            };
    });

但这需要更改您的接口以适应该属性,并且遍历许多元素应该比关联数组查找(字典)慢得多。
很高兴知道,即使不进行拆分,也可以完成此操作。

这些只是我的$ 0.05


如果服务已IDispose实施,谁负责处置服务?您已将字典注册为Singleton
LP13 '19

@ LP13:您还可以使用委托将字典注册为值,然后可以在itransient中注册它,并创建一个新实例,例如。GetRequiredService <T>()[“ logDB”]()
Stefan Steiger

5

从上面的帖子开始,我已经转到通用工厂类

用法

 services.AddFactory<IProcessor, string>()
         .Add<ProcessorA>("A")
         .Add<ProcessorB>("B");

 public MyClass(IFactory<IProcessor, string> processorFactory)
 {
       var x = "A"; //some runtime variable to select which object to create
       var processor = processorFactory.Create(x);
 }

实作

public class FactoryBuilder<I, P> where I : class
{
    private readonly IServiceCollection _services;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public FactoryBuilder(IServiceCollection services)
    {
        _services = services;
        _factoryTypes = new FactoryTypes<I, P>();
    }
    public FactoryBuilder<I, P> Add<T>(P p)
        where T : class, I
    {
        _factoryTypes.ServiceList.Add(p, typeof(T));

        _services.AddSingleton(_factoryTypes);
        _services.AddTransient<T>();
        return this;
    }
}
public class FactoryTypes<I, P> where I : class
{
    public Dictionary<P, Type> ServiceList { get; set; } = new Dictionary<P, Type>();
}

public interface IFactory<I, P>
{
    I Create(P p);
}

public class Factory<I, P> : IFactory<I, P> where I : class
{
    private readonly IServiceProvider _serviceProvider;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public Factory(IServiceProvider serviceProvider, FactoryTypes<I, P> factoryTypes)
    {
        _serviceProvider = serviceProvider;
        _factoryTypes = factoryTypes;
    }

    public I Create(P p)
    {
        return (I)_serviceProvider.GetService(_factoryTypes.ServiceList[p]);
    }
}

延期

namespace Microsoft.Extensions.DependencyInjection
{
    public static class DependencyExtensions
    {
        public static IServiceCollection AddFactory<I, P>(this IServiceCollection services, Action<FactoryBuilder<I, P>> builder)
            where I : class
        {
            services.AddTransient<IFactory<I, P>, Factory<I, P>>();
            var factoryBuilder = new FactoryBuilder<I, P>(services);
            builder(factoryBuilder);
            return services;
        }
    }
}

您可以提供.AddFactory()方法扩展吗?
开发人员

抱歉,刚刚看到了这个...添加
T布朗

3

@Miguel A. Arilla似乎已清楚地指出了这一点,我投了赞成票,但我在他有用的解决方案之上创建了另一个看起来很简洁但需要更多工作的解决方案。

绝对取决于以上解决方案。因此,基本上,我创建了类似于的东西Func<string, IService>>,并称其IServiceAccessor为接口,然后我必须对象这样添加更多扩展IServiceCollection

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

服务访问器如下所示:

 public interface IServiceAccessor<TService>
    {
         void Register(TService service,string name);
         TService Resolve(string name);

    }

最终结果是,您将能够使用名称或命名实例注册服务,就像我们以前处理其他容器时一样。例如:

    services.AddSingleton<IEncryptionService, SymmetricEncryptionService, EncyptionServiceAccessor>("Symmetric");
    services.AddSingleton<IEncryptionService, AsymmetricEncryptionService, EncyptionServiceAccessor>("Asymmetric");

到目前为止,这已经足够了,但是为了使您的工作更完整,最好添加更多扩展方法,因为您可以使用相同的方法覆盖所有类型的注册。

关于stackoverflow还有另一篇文章,但我找不到它,发帖人详细解释了为什么不支持此功能以及如何解决该问题,基本上类似于@Miguel所说的。即使我不同意每个观点,这也是一个不错的帖子,因为我认为在某些情况下您确实需要命名实例。再次找到该链接后,我将在此处发布该链接。

实际上,您不需要传递该选择器或访问器:

我在项目中使用以下代码,到目前为止效果很好。

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

1
这有助于解决我在服务访问器中丢失类型注册的问题。诀窍是删除服务访问者的所有绑定,然后再次添加!
Umar Farooq Khawaja

3

我为此创建了一个库,该库实现了一些不错的功能。可以在GitHub上找到代码:https : //github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https

用法很简单:

  1. 将Dazinator.Extensions.DependencyInjection nuget包添加到您的项目中。
  2. 添加您的命名服务注册。
    var services = new ServiceCollection();
    services.AddNamed<AnimalService>(names =>
    {
        names.AddSingleton("A"); // will resolve to a singleton instance of AnimalService
        names.AddSingleton<BearService>("B"); // will resolve to a singleton instance of BearService (which derives from AnimalService)
        names.AddSingleton("C", new BearService()); will resolve to singleton instance provided yourself.
        names.AddSingleton("D", new DisposableTigerService(), registrationOwnsInstance = true); // will resolve to singleton instance provided yourself, but will be disposed for you (if it implements IDisposable) when this registry is disposed (also a singleton).

        names.AddTransient("E"); // new AnimalService() every time..
        names.AddTransient<LionService>("F"); // new LionService() every time..

        names.AddScoped("G");  // scoped AnimalService
        names.AddScoped<DisposableTigerService>("H");  scoped DisposableTigerService and as it implements IDisposable, will be disposed of when scope is disposed of.

    });

在上面的示例中,请注意,对于每个命名的注册,您还指定了生存期或Singleton,Scoped或Transient。

您可以通过以下两种方式之一来解析服务,具体取决于您是否愿意让服务依赖于以下软件包:

public MyController(Func<string, AnimalService> namedServices)
{
   AnimalService serviceA = namedServices("A");
   AnimalService serviceB = namedServices("B"); // BearService derives from AnimalService
}

要么

public MyController(NamedServiceResolver<AnimalService> namedServices)
{
   AnimalService serviceA = namedServices["A"];
   AnimalService serviceB = namedServices["B"]; // instance of BearService returned derives from AnimalService
}

我专门设计了此库以与Microsoft.Extensions.DependencyInjection很好地配合使用-例如:

  1. 当您注册一个名为服务,您注册的任何类型可以有参数的构造函数-他们将通过DI满足,以同样的方式AddTransient<>AddScoped<>AddSingleton<>方法通常工作。

  2. 对于临时和作用域命名服务,注册表会构建一个,ObjectFactory以便可以在需要时快速激活该类型的新实例。这比其他方法快得多,并且与Microsoft.Extensions.DependencyInjection的执行方式一致。


2

我的解决方案值得考虑...考虑切换到温莎城堡,因为不能说我喜欢上面的任何解决方案。抱歉!!

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

创建各种实现

public class YourClassA : IStage<YouClassA> { 
    public void DoSomething() 
    {
        ...TODO
    }
}

public class YourClassB : IStage<YourClassB> { .....etc. }

注册

services.AddTransient<IStage<YourClassA>, YourClassA>()
services.AddTransient<IStage<YourClassB>, YourClassB>()

构造函数和实例用法...

public class Whatever
{
   private IStage ClassA { get; }

   public Whatever(IStage<YourClassA> yourClassA)
   {
         ClassA = yourClassA;
   }

   public void SomeWhateverMethod()
   {
        ClassA.DoSomething();
        .....
   }

1

扩展@rnrneverdies的解决方案。除了ToString(),还可以使用以下选项:1)具有公共属性实现,2)@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....
}

1

在阅读了这里的答案和其他地方的文章后,我可以不用字符串就可以使用它。当您使用同一接口的多个实现时,DI会将这些添加到集合中,因此可以使用从集合中检索所需的版本typeof

// In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(IService, ServiceA);
    services.AddScoped(IService, ServiceB);
    services.AddScoped(IService, ServiceC);
}

// Any class that uses the service(s)
public class Consumer
{
    private readonly IEnumerable<IService> _myServices;

    public Consumer(IEnumerable<IService> myServices)
    {
        _myServices = myServices;
    }

    public UseServiceA()
    {
        var serviceA = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceA));
        serviceA.DoTheThing();
    }

    public UseServiceB()
    {
        var serviceB = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceB));
        serviceB.DoTheThing();
    }

    public UseServiceC()
    {
        var serviceC = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceC));
        serviceC.DoTheThing();
    }
}

违反了IoC的目的。您不妨写下:var serviceA = new ServiceA();
James Curran,

2
@JamesCurran如果ServiceA具有依赖项,或者您要对类进行单元测试,则不会。
Jorn.Beyers,


0

服务服务怎么样?

如果我们有一个INamedService接口(具有.Name属性),则可以为.GetService(string name)编写一个IServiceCollection扩展,该扩展将采用该字符串参数,并对其自身执行.GetServices(),并在返回的每个参数中实例,找到其INamedService.Name与给定名称匹配的实例。

像这样:

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

因此,您的IMyService必须实现INamedService,但是您将获得所需的基于密钥的解决方案,对吗?

公平地讲,甚至必须拥有这个INamedService接口似乎很丑陋,但是如果您想走得更远并使事情变得更优雅,则可以在此代码中找到实现/类的[NamedServiceAttribute(“ A”)]扩展,效果也一样。更公平地说,反射速度很慢,因此可能需要进行优化,但是老实说,这是DI引擎应该一直在帮助的事情。速度和简单性是TCO的重要贡献。

总而言之,不需要显式工厂,因为“查找命名服务”是如此可重用的概念,并且工厂类不能扩展为解决方案。而且Func <>看起来不错,但是switch块是如此之,再一次,您编写Func的次数将与编写工厂的次数相同。以更少的代码开始简单,可重用的代码,如果事实证明并非如此,那么就变得复杂了。


2
除非您绝对必须这样做,否则这称为服务定位器模式,通常不是最好的选择
Joe Phillips

@JoePhillips您对为什么它不是一个好的解决方案有一些意见吗?我喜欢它的优雅。我唯一能想到的缺点是,每当您获得一个实例时,我都会创建一个实例。
彼得

2
@Peter的主要原因是因为使用它非常困难。如果要将serviceLocator对象传递到类中,则该类使用的依赖项根本不是很明显,因为它是从魔术“神”对象中获取所有依赖项的。想象一下,必须找到要更改的类型的引用。当您通过服务定位器对象获取所有内容时,该功能基本上消失了。构造函数注入更加清晰和可靠
乔·菲利普斯

我不知道。显而易见,这对我来说不是一件容易的事……因为如果我关心跟踪组件如何利用它们的依赖关系,那么我将为此进行单元测试……测试不仅涉及每个依赖关系,而且有助于我们理解如何需要每个依赖项。通过阅读构造函数,您还如何意识到这一点?!
Craig Brunetti

0

我遇到了同样的问题,我使用一个简单的扩展来允许命名服务。你可以在这里找到它:

它允许您像这样添加任意数量的(命名)服务:

 var serviceCollection = new ServiceCollection();
 serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), "A", ServiceLifetime.Transient);
 serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), "B", ServiceLifetime.Transient);

 var serviceProvider = serviceCollection.BuildServiceProvider();

 var myServiceA = serviceProvider.GetService<IMyService>("A");
 var myServiceB = serviceProvider.GetService<IMyService>("B");

该库还允许您轻松实现“工厂模式”,如下所示:

    [Test]
    public void FactoryPatternTest()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), MyEnum.A.GetName(), ServiceLifetime.Transient);
        serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), MyEnum.B.GetName(), ServiceLifetime.Transient);

        serviceCollection.AddTransient<IMyServiceFactoryPatternResolver, MyServiceFactoryPatternResolver>();

        var serviceProvider = serviceCollection.BuildServiceProvider();

        var factoryPatternResolver = serviceProvider.GetService<IMyServiceFactoryPatternResolver>();

        var myServiceA = factoryPatternResolver.Resolve(MyEnum.A);
        Assert.NotNull(myServiceA);
        Assert.IsInstanceOf<MyServiceA>(myServiceA);

        var myServiceB = factoryPatternResolver.Resolve(MyEnum.B);
        Assert.NotNull(myServiceB);
        Assert.IsInstanceOf<MyServiceB>(myServiceB);
    }

    public interface IMyServiceFactoryPatternResolver : IFactoryPatternResolver<IMyService, MyEnum>
    {
    }

    public class MyServiceFactoryPatternResolver : FactoryPatternResolver<IMyService, MyEnum>, IMyServiceFactoryPatternResolver
    {
        public MyServiceFactoryPatternResolver(IServiceProvider serviceProvider)
        : base(serviceProvider)
        {
        }
    }

    public enum MyEnum
    {
        A = 1,
        B = 2
    }

希望能帮助到你


0

我在IServiceCollection使用的WithName扩展名上创建了自己的扩展名:

public static IServiceCollection AddScopedWithName<TService, TImplementation>(this IServiceCollection services, string serviceName)
        where TService : class
        where TImplementation : class, TService
    {
        Type serviceType = typeof(TService);
        Type implementationServiceType = typeof(TImplementation);
        ServiceCollectionTypeMapper.Instance.AddDefinition(serviceType.Name, serviceName, implementationServiceType.AssemblyQualifiedName);
        services.AddScoped<TImplementation>();
        return services;
    }

ServiceCollectionTypeMapper是一个单实例地图IService> NameOfService> Implementation其中一个接口可以有不同的名字许多实现,这使得注册类型不是我们可以解决在凌晨需求,是一种不同的方法不是解决多种服务选择我们想要的。

 /// <summary>
/// Allows to set the service register mapping.
/// </summary>
public class ServiceCollectionTypeMapper
{
    private ServiceCollectionTypeMapper()
    {
        this.ServiceRegister = new Dictionary<string, Dictionary<string, string>>();
    }

    /// <summary>
    /// Gets the instance of mapper.
    /// </summary>
    public static ServiceCollectionTypeMapper Instance { get; } = new ServiceCollectionTypeMapper();

    private Dictionary<string, Dictionary<string, string>> ServiceRegister { get; set; }

    /// <summary>
    /// Adds new service definition.
    /// </summary>
    /// <param name="typeName">The name of the TService.</param>
    /// <param name="serviceName">The TImplementation name.</param>
    /// <param name="namespaceFullName">The TImplementation AssemblyQualifiedName.</param>
    public void AddDefinition(string typeName, string serviceName, string namespaceFullName)
    {
        if (this.ServiceRegister.TryGetValue(typeName, out Dictionary<string, string> services))
        {
            if (services.TryGetValue(serviceName, out _))
            {
                throw new InvalidOperationException($"Exists an implementation with the same name [{serviceName}] to the type [{typeName}].");
            }
            else
            {
                services.Add(serviceName, namespaceFullName);
            }
        }
        else
        {
            Dictionary<string, string> serviceCollection = new Dictionary<string, string>
            {
                { serviceName, namespaceFullName },
            };
            this.ServiceRegister.Add(typeName, serviceCollection);
        }
    }

    /// <summary>
    /// Get AssemblyQualifiedName of implementation.
    /// </summary>
    /// <typeparam name="TService">The type of the service implementation.</typeparam>
    /// <param name="serviceName">The name of the service.</param>
    /// <returns>The AssemblyQualifiedName of the inplementation service.</returns>
    public string GetService<TService>(string serviceName)
    {
        Type serviceType = typeof(TService);

        if (this.ServiceRegister.TryGetValue(serviceType.Name, out Dictionary<string, string> services))
        {
            if (services.TryGetValue(serviceName, out string serviceImplementation))
            {
                return serviceImplementation;
            }
            else
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }

要注册新服务:

services.AddScopedWithName<IService, MyService>("Name");

为了解决服务,我们需要IServiceProvider像这样的扩展。

/// <summary>
    /// Gets the implementation of service by name.
    /// </summary>
    /// <typeparam name="T">The type of service.</typeparam>
    /// <param name="serviceProvider">The service provider.</param>
    /// <param name="serviceName">The service name.</param>
    /// <returns>The implementation of service.</returns>
    public static T GetService<T>(this IServiceProvider serviceProvider, string serviceName)
    {
        string fullnameImplementation = ServiceCollectionTypeMapper.Instance.GetService<T>(serviceName);
        if (fullnameImplementation == null)
        {
            throw new InvalidOperationException($"Unable to resolve service of type [{typeof(T)}] with name [{serviceName}]");
        }
        else
        {
            return (T)serviceProvider.GetService(Type.GetType(fullnameImplementation));
        }
    }

解决时:

serviceProvider.GetService<IWithdrawalHandler>(serviceName);

请记住,可以将serviceProvider注入到我们应用程序的构造函数中,如下所示: IServiceProvider

我希望这有帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.