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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在Asp.Net Core中注册同一接口的多个实现?相关的知识,希望对你有一定的参考价值。

我有来自相同接口的服务

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

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

在Asp.Net Core中,我如何注册这些服务并在运行时根据某些键解决它?

我没有看到任何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
       
    

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

UPDATE1 我已经删除了文章here,它展示了当我们有多个concreate实现时如何使用工厂模式来获取服务实例。但它仍然不是完整的解决方案。当我调用_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),因为我必须使用新容器注册其他所有内容(例如控制器)。

同时使用工厂模式创建服务实例是针对DIP的,因为工厂增加了客户端被迫依赖于details here的依赖项数量

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

答案

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

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

并在DI注册的任何类中使用它,如:

public class Consumer

    private readonly Func<string, IService> serviceAccessor;

    public Consumer(Func<string, IService> serviceAccessor)
    
        this.serviceAccessor = serviceAccesor;
    

    public void UseServiceA()
    
        //use serviceAccessor field to resolve desired type
        serviceAccessor("A").DoIServiceOperation();
    

UPDATE

请记住,在这个例子中,解析的关键是一个字符串,为了简单起见,因为OP特别要求这个案例。

但是你可以使用任何自定义分辨率类型作为键,因为你通常不希望一个巨大的n-case开关腐烂你的代码。取决于您的应用如何扩展。

另一答案

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

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();
        .....
   
另一答案

这次聚会迟到了,但这是我的解决方案:......

如果是Generic Handler,则为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();

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

另一答案

Necromancing。 我认为这里的人们正在重新发明轮子 - 如果我可以这么说的话,那就太糟糕了...... 如果要按键注册组件,只需使用字典:

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



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

但这需要更改您的接口以适应属性,并且循环遍历许多元素应该比关联数组查找(字典)慢得多。 不过,很高兴知道它可以在没有指令的情况下完成。

这些只是我0.05美元

另一答案

虽然开箱即用的实现不提供它,但这是一个示例项目,允许您注册命名实例,然后将INamedServiceFactory注入代码并按名称提取实例。与此处的其他辅助解决方案不同,它允许您注册相同实现的多个实例,但配置不同

https://github.com/macsux/DotNetDINamedInstances

另一答案

服务服务怎么样?

如果我们有一个INamedService接口(具有.Name属性),我们可以为.GetService(字符串名称)编写一个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接口看起来很丑,但是如

以上是关于如何在Asp.Net Core中注册同一接口的多个实现?的主要内容,如果未能解决你的问题,请参考以下文章

Asp.NET Core 一个接口的多个实现如何通过 DI 注册?

如何在 ASP.NET Core MVC 的同一视图中添加/创建多个一对多关系

如何在 ASP.NET Core 中为同一接口配置不同的实现

如何远程关闭一个ASP.NET Core应用?

如何远程关闭一个ASP.NET Core应用?

[Asp.Net Core]Autofac单抽象多实现构造函数注入