如何在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
服务方法采用通常用于区分具体实现的key
或name
参数。
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 的同一视图中添加/创建多个一对多关系