一尘不染

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

c#

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

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. 在注册过程中将静态数据注入构造函数的能力

阅读 760

收藏
2020-05-19

共1个答案

一尘不染

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开关来破坏您的代码。取决于您的应用扩展方式。

2020-05-19