[英]How to register multiple implementations with its own interface in ASP.NET Core using Reflection?
[英]How to register multiple implementations of the same interface in Asp.Net Core?
我有从相同接口派生的服务。
public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { }
public class ServiceC : IService { }
通常,其他 IoC 容器(如Unity
)允许您通过一些区分它们的Key
来注册具体实现。
在 ASP.NET Core 中,我如何注册这些服务并在运行时根据一些键来解决它们?
我没有看到任何采用key
或name
参数的Add
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?
}
}
工厂模式是这里唯一的选择吗?
更新1
我已经阅读了这里的文章,该文章展示了当我们有多个具体实现时如何使用工厂模式来获取服务实例。 但是,它仍然不是一个完整的解决方案。 当我调用_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 的,因为它会增加客户端在 此处的详细信息的依赖项数量。
所以,我认为 ASP.NET Core 中的默认 DI 缺少两件事:
当我发现自己处于这种情况时,我使用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 开关腐烂您的代码。 取决于您的应用程序的扩展方式。
另一种选择是使用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”的字符串属性)
确保using Microsoft.Extensions.DependencyInjection;
AspNet 2.1 来源: GetServices
工厂方法当然是可行的。 另一种方法是使用继承来创建从 IService 继承的单个接口,在 IService 实现中实现继承的接口,并注册继承的接口而不是基接口。 添加继承层次结构或工厂是否是“正确”模式完全取决于您与谁交谈。 在使用泛型(例如IRepository<T>
)作为数据访问基础的同一应用程序中处理多个数据库提供程序时,我经常不得不使用这种模式。
示例接口和实现:
public interface IService
{
}
public interface IServiceA: IService
{}
public interface IServiceB: IService
{}
public interface 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>();
我只是简单地注入一个 IEnumerable
Startup.cs 中的配置服务
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"; } }
}
我的控制器.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();
}
...
}
扩展.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;
}
这个聚会有点晚了,但这是我的解决方案:...
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();
}
访问控制器中的服务
public class MyController
{
private readonly IMyInterface<CustomerSavedConsumer> _customerSavedConsumer;
private readonly IMyInterface<ManagerSavedConsumer> _managerSavedConsumer;
public MyController(IMyInterface<CustomerSavedConsumer> customerSavedConsumer, IMyInterface<ManagerSavedConsumer> managerSavedConsumer)
{
_customerSavedConsumer = customerSavedConsumer;
_managerSavedConsumer = managerSavedConsumer;
}
}
希望如果这样做有任何问题,有人会指出为什么这是错误的方法。
这里的大多数答案都违反了单一责任原则(服务类本身不应解决依赖关系)和/或使用服务定位器反模式。
避免这些问题的另一个选择是:
我写了一篇更详细的文章: .NET 中的依赖注入:一种解决缺少命名注册的方法
Microsoft.Extensions.DependencyInjection
不支持它。
但是你可以插入另一种依赖注入机制,比如StructureMap
见它的主页和它的GitHub 项目。
一点都不难:
在project.json
中添加对 StructureMap 的依赖项:
"Structuremap.Microsoft.DependencyInjection" : "1.0.1",
将其注入到ConfigureServices
内的 ASP.NET 管道中并注册您的类(请参阅文档)
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>(); }
然后,要获取命名实例,您需要请求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; }
}
为什么不使用继承? 这样,我们可以拥有任意数量的接口副本,并且可以为每个副本选择合适的名称。 而且我们有类型安全的好处
public interface IReportGenerator
public interface IExcelReportGenerator : IReportGenerator
public interface IPdfReportGenerator : IReportGenerator
具体类:
public class ExcelReportGenerator : IExcelReportGenerator
public class PdfReportGenerator : IPdfReportGenerator
登记:
代替
services.AddScoped<IReportGenerator, PdfReportGenerator>();
services.AddScoped<IReportGenerator, ExcelReportGenerator>();
我们有 :
services.AddScoped<IPdfReportGenerator, PdfReportGenerator>();
services.AddScoped<IExcelReportGenerator, ExcelReportGenerator>();
客户:
public class ReportManager : IReportManager
{
private readonly IExcelReportGenerator excelReportGenerator;
private readonly IPdfReportGenerator pdfReportGenerator;
public ReportManager(IExcelReportGenerator excelReportGenerator,
IPdfReportGenerator pdfReportGenerator)
{
this.excelReportGenerator = excelReportGenerator;
this.pdfReportGenerator = pdfReportGenerator;
}
这种方法还允许使用虱子耦合代码,因为我们可以将 IReportGenerator 移至应用程序的核心,并拥有将在更高级别声明的子接口。
我遇到了同样的问题,想分享一下我是如何解决的以及为什么。
正如你提到的,有两个问题。 首先:
在 Asp.Net Core 中,我如何注册这些服务并在运行时根据某些键解决它?
那么我们有哪些选择呢? 网友们建议两个:
使用自定义工厂(如_myFactory.GetServiceByKey(key)
)
使用另一个 DI 引擎(如_unityContainer.Resolve<IService>(key)
)
工厂模式是这里唯一的选择吗?
事实上,这两个选项都是工厂,因为每个 IoC 容器也是工厂(尽管高度可配置且复杂)。 在我看来,其他选项也是工厂模式的变体。
那么什么选择更好呢? 在这里,我同意@Sock 的建议,他建议使用自定义工厂,这就是原因。
首先,我总是尽量避免在不需要时添加新的依赖项。 所以我同意你的观点。 此外,使用两个 DI 框架比创建自定义工厂抽象更糟糕。 在第二种情况下,您必须添加新的包依赖项(如 Unity),但在这里依赖新的工厂接口并不那么邪恶。 我相信 ASP.NET Core DI 的主要思想是简单。 它按照KISS 原则维护了一组最小的功能。 如果您需要一些额外的功能,那么 DIY 或使用实现所需功能的相应Plungin (开放封闭原则)。
其次,通常我们需要为单个服务注入许多命名依赖项。 在 Unity 的情况下,您可能必须为构造函数参数指定名称(使用InjectionConstructor
)。 此注册使用反射和一些智能逻辑来猜测构造函数的参数。 如果注册与构造函数参数不匹配,这也可能导致运行时错误。 另一方面,在使用自己的工厂时,您可以完全控制如何提供构造函数参数。 它更具可读性,并在编译时解决。 再次KISS原则。
第二个问题:
_serviceProvider.GetService() 如何注入适当的连接字符串?
首先,我同意你的观点,依赖于IOptions
等新事物(因此依赖于包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>())));
死灵术。
我认为这里的人们正在重新发明轮子——而且很糟糕,如果我可以这么说的话......
如果要按键注册组件,只需使用字典:
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"));
然后使用服务集合注册字典:
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 美元
自从我上面的帖子以来,我已经搬到了一个通用工厂类
用法
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 FactoryBuilder<I, P> AddFactory<I, P>(this IServiceCollection services)
where I : class
{
services.AddTransient<IFactory<I, P>, Factory<I, P>>();
return new FactoryBuilder<I, P>(services);
}
}
}
显然,您可以只注入服务接口的 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; }
}
应用设置.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();
}
.........
}
这是一个关于如何创建依赖关系解析器的示例,它允许您指定一个通用参数来解析您的依赖关系。
var serviceProvider = new ServiceCollection()
.AddSingleton<IPerson, Larry>()
.AddSingleton<IPerson, Phil>()
.AddSingleton<IDependencyResolver<IPerson, string>, PersonDependecyResolver>()
.BuildServiceProvider();
var persons = serviceProvider.GetService<IDependencyResolver<IPerson, string>>();
Console.WriteLine(persons.GetDependency("Phil").GetName());
public interface IDependencyResolver<out TResolve, in TArg>
{
TResolve GetDependency(TArg arg);
}
public class PersonDependecyResolver : IDependencyResolver<IPerson, string>
{
private readonly IEnumerable<IPerson> people;
public PersonDependecyResolver(IEnumerable<IPerson> people)
{
this.people = people;
}
public IPerson GetDependency(string arg)
{
return arg switch
{
"Larry" => this.people.FirstOrDefault(p => p.GetType() == typeof(Larry)),
"Phil" => this.people.FirstOrDefault(p => p.GetType() == typeof(Phil)),
_ => throw new Exception("Unable to resolve dependency")
}
?? throw new Exception($"No type was found for argument {arg}");
}
}
我的价值解决方案......考虑切换到温莎城堡,因为不能说我喜欢上面的任何解决方案。 对不起!!
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();
.....
}
我没有时间通读它们,但似乎每个人都在为原本不应该存在的问题提供解决方案。
如果您需要所有已注册的 IService 实现,那么您就需要它们。 但不要将它们全部注入 IEnumerable,然后使用逻辑根据某种类型的键选择一个。 这样做的问题是您需要一个密钥,如果密钥更改,则逻辑不需要更改,即; IService 的不同实现,因此 typeof 不再起作用。
真正的问题
这里有应该在引擎服务中的业务逻辑。 需要像 IServiceDecisionEngine 这样的东西。 IServiceDecisionEngine 的实现仅从 DI 获得所需的 IService 实现。 喜欢
public class ServiceDecisionEngine<SomeData>: IServiceDecisionEngine<T>
{
public ServiceDecisionEngine(IService serviceA, IService serviceB) { }
public IService ResolveService(SomeData dataNeededForLogic)
{
if (dataNeededForLogic.someValue == true)
{
return serviceA;
}
return serviceB;
}
}
现在在您的 DI 中,您可以执行.AddScoped<IServiceDecisionEngine<SomeData>, new ServiceDecisionEngine(new ServiceA(), new ServiceB())
并且需要 IService 的 managerService 将通过注入和使用 IServiceDecisionEngine 来获得它。
我认为以下文章“ Resolución dinámica de Tipos en tiempo de ejecución en el contenedor de IoC de .NET Core ”中描述的解决方案更简单,不需要工厂。
您可以使用通用接口
public interface IService<T> where T : class {}
然后在 IoC 容器上注册所需的类型:
services.AddTransient<IService<ServiceA>, ServiceA>();
services.AddTransient<IService<ServiceB>, ServiceB>();
之后,您必须按如下方式声明依赖项:
private readonly IService<ServiceA> _serviceA;
private readonly IService<ServiceB> _serviceB;
public WindowManager(IService<ServiceA> serviceA, IService<ServiceB> serviceB)
{
this._serviceA = serviceA ?? throw new ArgumentNullException(nameof(serviceA));
this._serviceB = serviceB ?? throw new ArgumentNullException(nameof(ServiceB));
}
我有同样的问题,我用<T>
解决了
我的界面:
public interface IProvider<T>
{
Task<string> GetTotalSearchResults(string searchValue);
}
我的服务配置:
var host = Host.CreateDefaultBuilder()
.ConfigureServices((_, services) =>
{
services.AddSingleton(googleSettings);
services.AddSingleton(bingSettings);
services.AddSingleton<IProvider<BingProvider>, BingProvider>();
services.AddSingleton<IProvider<GoogleProvider>, GoogleProvider>();
services.AddSingleton<ISearchManager, SearchManager>();
});
您可以在课堂上使用它:
public class SearchManager : ISearchManager
{
private readonly IProvider<BingProvider> _bing;
private readonly IProvider<GoogleProvider> _google;
public SearchManager(IProvider<BingProvider> bing, IProvider<GoogleProvider> google)
{
_bing = bing;
_google = google;
}
虽然@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 所说的基本相似。 尽管我不同意每一点,但这篇文章还是不错的,因为我认为在某些情况下您确实需要命名实例。 再次找到该链接后,我将在此处发布该链接。
事实上,您不需要传递该 Selector 或 Accessor:
我在我的项目中使用以下代码,到目前为止它运行良好。
/// <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;
}
我为此创建了一个库,它实现了一些不错的功能。 代码可以在 GitHub 上找到: https ://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https ://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/
用法很简单:
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。
您可以通过以下两种方式之一来解析服务,具体取决于您是否愿意让您的服务依赖此包:not:
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 配合使用 - 例如:
当您注册命名服务时,您注册的任何类型都可以具有带参数的构造函数 - 它们将通过 DI 得到满足,就像AddTransient<>
、 AddScoped<>
和AddSingleton<>
方法通常工作一样。
对于瞬态和作用域命名服务,注册中心构建一个ObjectFactory
以便它可以在需要时非常快速地激活该类型的新实例。 这比其他方法快得多,并且符合 Microsoft.Extensions.DependencyInjection 的处理方式。
我知道这篇文章已经有几年的历史了,但我一直遇到这个问题,我对服务定位器模式不满意。
另外,我知道 OP 正在寻找一种实现,它允许您基于字符串选择具体的实现。 我也意识到 OP 专门要求实现相同的接口。 我将要描述的解决方案依赖于向你的接口添加一个泛型类型参数。 问题是除了服务集合绑定之外,类型参数没有任何实际用途。 我将尝试描述可能需要这样的情况。
想象一下 appsettings.json 中这种场景的配置,它可能看起来像这样(这只是为了演示,只要您有正确的配置提供程序,您的配置就可以来自您想要的任何地方):
{
"sqlDataSource": {
"connectionString": "Data Source=localhost; Initial catalog=Foo; Connection Timeout=5; Encrypt=True;",
"username": "foo",
"password": "this normally comes from a secure source, but putting here for demonstration purposes"
},
"mongoDataSource": {
"hostName": "uw1-mngo01-cl08.company.net",
"port": 27026,
"collection": "foo"
}
}
您确实需要一个代表每个配置选项的类型:
public class SqlDataSource
{
public string ConnectionString { get;set; }
public string Username { get;set; }
public string Password { get;set; }
}
public class MongoDataSource
{
public string HostName { get;set; }
public string Port { get;set; }
public string Collection { get;set; }
}
现在,我知道拥有相同接口的两个实现似乎有点做作,但我肯定在不止一种情况下见过它。 我经常遇到的有:
无论如何,您可以通过向服务接口添加类型参数来引用它们,以便您可以实现不同的实现:
public interface IService<T> {
void DoServiceOperation();
}
public class MongoService : IService<MongoDataSource> {
private readonly MongoDataSource _options;
public FooService(IOptionsMonitor<MongoDataSource> serviceOptions){
_options = serviceOptions.CurrentValue
}
void DoServiceOperation(){
//do something with your mongo data source options (connect to database)
throw new NotImplementedException();
}
}
public class SqlService : IService<SqlDataSource> {
private readonly SqlDataSource_options;
public SqlService (IOptionsMonitor<SqlDataSource> serviceOptions){
_options = serviceOptions.CurrentValue
}
void DoServiceOperation(){
//do something with your sql data source options (connect to database)
throw new NotImplementedException();
}
}
在启动时,您将使用以下代码注册这些:
services.Configure<SqlDataSource>(configurationSection.GetSection("sqlDataSource"));
services.Configure<MongoDataSource>(configurationSection.GetSection("mongoDataSource"));
services.AddTransient<IService<SqlDataSource>, SqlService>();
services.AddTransient<IService<MongoDataSource>, MongoService>();
最后,在依赖于具有不同连接的 Service 的类中,您只需依赖所需的服务,DI 框架将处理其余的:
[Route("api/v1)]
[ApiController]
public class ControllerWhichNeedsMongoService {
private readonly IService<MongoDataSource> _mongoService;
private readonly IService<SqlDataSource> _sqlService ;
public class ControllerWhichNeedsMongoService(
IService<MongoDataSource> mongoService,
IService<SqlDataSource> sqlService
)
{
_mongoService = mongoService;
_sqlService = sqlService;
}
[HttpGet]
[Route("demo")]
public async Task GetStuff()
{
if(useMongo)
{
await _mongoService.DoServiceOperation();
}
await _sqlService.DoServiceOperation();
}
}
这些实现甚至可以相互依赖。 另一个很大的好处是您可以获得编译时绑定,因此任何重构工具都可以正常工作。
希望这对将来的某人有所帮助。
答案很晚,但这是我这样做的方式,与这个问题的其他一些解决方案相比,它具有一些优势。
优点:
Func<TKey, TService>
注入,但如果您愿意,可以轻松注册自定义委托或类型int
、 string
、 enum
或bool
,因为为什么要让生活变得比需要的更复杂)配置示例:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// default instantiation:
services.AddKeyedService<IService, ImplementationA, string>("A", ServiceLifetime.Scoped);
// using an implementation factory to pass a connection string to the constructor:
services.AddKeyedService<IService, ImplementationB, string>("B", x => {
var connectionString = ConfigurationManager.ConnectionStrings["mongo"].ConnectionString;
return new ImplementationB(connectionString);
}, ServiceLifetime.Scoped);
// using a custom delegate instead of Func<TKey, TService>
services.AddKeyedService<IService, ImplementationC, string, StringKeyedService>(
"C", (_, x) => new StringKeyedService(x), ServiceLifetime.Singleton);
return services.BuildServiceProvider();
}
public delegate IService StringKeyedService(string key);
使用示例:
public ExampleClass(Func<string, IService> keyedServiceFactory, StringKeyedService<IService> keyedServiceDelegate)
{
var serviceKey = Configuration.GetValue<string>("IService.Key");
var service = keyedServiceFactory(serviceKey);
var serviceC = keyedServiceDelegate("C");
}
执行:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;
public static class KeyedServiceExtensions
{
// Use this to register TImplementation as TService, injectable as Func<TKey, TService>.
// Uses default instance activator.
public static IServiceCollection AddKeyedService<TService, TImplementation, TKey>(this IServiceCollection services, TKey key, ServiceLifetime serviceLifetime)
where TService : class
where TImplementation : class, TService
{
services.AddTransient<TImplementation>();
var keyedServiceBuilder = services.CreateOrUpdateKeyedServiceBuilder<TKey, TService, Func<TKey, TService>>(
DefaultImplementationFactory<TKey, TService>, serviceLifetime);
keyedServiceBuilder.Add<TImplementation>(key);
return services;
}
// Use this to register TImplementation as TService, injectable as Func<TKey, TService>.
// Uses implementationFactory to create instances
public static IServiceCollection AddKeyedService<TService, TImplementation, TKey>(this IServiceCollection services, TKey key,
Func<IServiceProvider, TImplementation> implementationFactory, ServiceLifetime serviceLifetime)
where TService : class
where TImplementation : class, TService
{
services.AddTransient(implementationFactory);
var keyedServiceBuilder = services.CreateOrUpdateKeyedServiceBuilder<TKey, TService, Func<TKey, TService>>(
DefaultImplementationFactory<TKey, TService>, serviceLifetime);
keyedServiceBuilder.Add<TImplementation>(key);
return services;
}
// Use this to register TImplementation as TService, injectable as TInjection.
// Uses default instance activator.
public static IServiceCollection AddKeyedService<TService, TImplementation, TKey, TInjection>(this IServiceCollection services, TKey key,
Func<IServiceProvider, Func<TKey, TService>, TInjection> serviceFactory, ServiceLifetime serviceLifetime)
where TService : class
where TImplementation : class, TService
where TInjection : class
{
services.AddTransient<TImplementation>();
var keyedServiceBuilder = services.CreateOrUpdateKeyedServiceBuilder<TKey, TService, TInjection>(
x => serviceFactory(x, DefaultImplementationFactory<TKey, TService>(x)), serviceLifetime);
keyedServiceBuilder.Add<TImplementation>(key);
return services;
}
// Use this to register TImplementation as TService, injectable as TInjection.
// Uses implementationFactory to create instances
public static IServiceCollection AddKeyedService<TService, TImplementation, TKey, TInjection>(this IServiceCollection services, TKey key,
Func<IServiceProvider, TImplementation> implementationFactory, Func<IServiceProvider, Func<TKey, TService>, TInjection> serviceFactory, ServiceLifetime serviceLifetime)
where TService : class
where TImplementation : class, TService
where TInjection : class
{
services.AddTransient(implementationFactory);
var keyedServiceBuilder = services.CreateOrUpdateKeyedServiceBuilder<TKey, TService, TInjection>(
x => serviceFactory(x, DefaultImplementationFactory<TKey, TService>(x)), serviceLifetime);
keyedServiceBuilder.Add<TImplementation>(key);
return services;
}
private static KeyedServiceBuilder<TKey, TService> CreateOrUpdateKeyedServiceBuilder<TKey, TService, TInjection>(this IServiceCollection services,
Func<IServiceProvider, TInjection> serviceFactory, ServiceLifetime serviceLifetime)
where TService : class
where TInjection : class
{
var builderServiceDescription = services.SingleOrDefault(x => x.ServiceType == typeof(KeyedServiceBuilder<TKey, TService>));
KeyedServiceBuilder<TKey, TService> keyedServiceBuilder;
if (builderServiceDescription is null)
{
keyedServiceBuilder = new KeyedServiceBuilder<TKey, TService>();
services.AddSingleton(keyedServiceBuilder);
switch (serviceLifetime)
{
case ServiceLifetime.Singleton:
services.AddSingleton(serviceFactory);
break;
case ServiceLifetime.Scoped:
services.AddScoped(serviceFactory);
break;
case ServiceLifetime.Transient:
services.AddTransient(serviceFactory);
break;
default:
throw new ArgumentOutOfRangeException(nameof(serviceLifetime), serviceLifetime, "Invalid value for " + nameof(serviceLifetime));
}
}
else
{
CheckLifetime<KeyedServiceBuilder<TKey, TService>>(builderServiceDescription.Lifetime, ServiceLifetime.Singleton);
var factoryServiceDescriptor = services.SingleOrDefault(x => x.ServiceType == typeof(TInjection));
CheckLifetime<TInjection>(factoryServiceDescriptor.Lifetime, serviceLifetime);
keyedServiceBuilder = (KeyedServiceBuilder<TKey, TService>)builderServiceDescription.ImplementationInstance;
}
return keyedServiceBuilder;
static void CheckLifetime<T>(ServiceLifetime actual, ServiceLifetime expected)
{
if (actual != expected)
throw new ApplicationException($"{typeof(T).FullName} is already registered with a different ServiceLifetime. Expected: '{expected}', Actual: '{actual}'");
}
}
private static Func<TKey, TService> DefaultImplementationFactory<TKey, TService>(IServiceProvider x) where TService : class
=> x.GetRequiredService<KeyedServiceBuilder<TKey, TService>>().Build(x);
private sealed class KeyedServiceBuilder<TKey, TService>
{
private readonly Dictionary<TKey, Type> _serviceImplementationTypes = new Dictionary<TKey, Type>();
internal void Add<TImplementation>(TKey key) where TImplementation : class, TService
{
if (_serviceImplementationTypes.TryGetValue(key, out var type) && type == typeof(TImplementation))
return; //this type is already registered under this key
_serviceImplementationTypes[key] = typeof(TImplementation);
}
internal Func<TKey, TService> Build(IServiceProvider serviceProvider)
{
var serviceTypeDictionary = _serviceImplementationTypes.Values.Distinct()
.ToDictionary(
type => type,
type => new Lazy<TService>(
() => (TService)serviceProvider.GetRequiredService(type),
LazyThreadSafetyMode.ExecutionAndPublication
)
);
var serviceDictionary = _serviceImplementationTypes
.ToDictionary(kvp => kvp.Key, kvp => serviceTypeDictionary[kvp.Value]);
return key => serviceDictionary[key].Value;
}
}
}
也可以在此之上制作一个流畅的界面,如果有兴趣请告诉我。
流体使用示例:
var keyedService = services.KeyedSingleton<IService, ServiceKey>()
.As<ICustomKeyedService<TKey, IService>>((_, x) => new CustomKeyedServiceInterface<ServiceKey, IService>(x));
keyedService.Key(ServiceKey.A).Add<ServiceA>();
keyedService.Key(ServiceKey.B).Add(x => {
x.GetService<ILogger>.LogDebug("Instantiating ServiceB");
return new ServiceB();
});
任何使用IEnumerable<Interface>
的技术方法都有效地违背了 DI 的全部目的,因为您需要选择需要解决的实现,并且可能指向糟糕的设计。
对我有用的这个问题的解决方法是分开使用并创建单独的接口,像这样
public interface ICRUDService<T> where T : class
{
void CreateAndSetId(T item);
void Delete(int id);
ActionResult<List<T>> GetAll();
ActionResult<T> GetById(int id);
void Update(int id, T item);
}
然后是各个接口
public interface ITodoService : ICRUDService<Todo> {}
public interface IValuesService : ICRUDService<Values> {}
以及他们的实现
public class TodoService : ITodoService { ... }
public class ValuesService : IValuesService { ... }
启动.ConfigureServices
services.AddScoped<ITodoService, TodoService>();
services.AddScoped<IValuesService, ValuesService>();
用法
public class UsageClass {
public UsageClass(ITodoService todoService, IValuesService valuesService) {}
}
如果您仍然对解决多个实现感兴趣, 这是 Microsoft 的建议。 只是在这里链接它,因为这不是我推荐的。
我为多个实现找到的最佳文档/教程来自以下来源: .NET Core Dependency Injection - One Interface, Multiple Implementations,(由 Akshay Patel 撰写)
教程中提到的示例遵循 Controller/Service/Repository 约定,在 Startup.cs 的 ConfigurationService() 中使用Func实现来实例化正确/需要的接口实现; 教程是我发现澄清这个问题的最佳配方。
下面是上述文章的粗略复制/粘贴:(示例处理购物车接口的 3 种不同实现,一种使用缓存解决方案的方法,另一种使用 API 和其他使用 DB 的实现。 )
接口被多重实现......
namespace MultipleImplementation
{
public interface IShoppingCart
{
object GetCart();
}
}
实施A
namespace MultipleImplementation
{
public class ShoppingCartCache : IShoppingCart
{
public object GetCart()
{
return "Cart loaded from cache.";
}
}
}
实施 B
namespace MultipleImplementation
{
public class ShoppingCartDB : IShoppingCart
{
public object GetCart()
{
return "Cart loaded from DB";
}
}
}
实施 C
namespace MultipleImplementation
{
public class ShoppingCartAPI : IShoppingCart
{
public object GetCart()
{
return "Cart loaded through API.";
}
}
}
将使用存储库中的接口声明来选择 A,B,C....
namespace MultipleImplementation
{
public interface IShoppingCartRepository
{
object GetCart();
}
}
枚举以选择将使用哪个实现...
namespace MultipleImplementation
{
public class Constants
{
}
public enum CartSource
{
Cache=1,
DB=2,
API=3
}
}
声明的存储库接口的实现(谁将选择哪个实现...... )
using System;
namespace MultipleImplementation
{
public class ShoppingCartRepository : IShoppingCartRepository
{
private readonly Func<string, IShoppingCart> shoppingCart;
public ShoppingCartRepository(Func<string, IShoppingCart> shoppingCart)
{
this.shoppingCart = shoppingCart;
}
public object GetCart()
{
return shoppingCart(CartSource.DB.ToString()).GetCart();
}
}
}
最后,将所有内容打包到startup.cs文件中,在ConfigureService方法中
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IShoppingCartRepository, ShoppingCartRepository>();
services.AddSingleton<ShoppingCartCache>();
services.AddSingleton<ShoppingCartDB>();
services.AddSingleton<ShoppingCartAPI>();
services.AddTransient<Func<string, IShoppingCart>>(serviceProvider => key =>
{
switch (key)
{
case "API":
return serviceProvider.GetService<ShoppingCartAPI>();
case "DB":
return serviceProvider.GetService<ShoppingCartDB>();
default:
return serviceProvider.GetService<ShoppingCartCache>();
}
});
services.AddMvc();
}
在那里,我强调,6 分钟的阅读将帮助您在一个界面中解决多个实现。 祝你好运!
扩展@rneverdies 的解决方案。 除了 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....
}
在阅读了这里的答案和其他地方的文章之后,我能够让它在没有字符串的情况下工作。 当您有相同接口的多个实现时,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();
}
}
我通过使用WithName
扩展的IServiceCollection
创建了自己的扩展:
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
注入到我们应用程序的构造函数中。
我希望这有帮助。
好的,这是使用字典的清晰易读的答案
使用您的数据库键名创建一个枚举
public enum Database
{
Red,
Blue
}
在 Startup.cs 中,创建一个打开新 SqlConnection 的函数字典,然后将依赖字典注入为 Singleton
Dictionary<Database, Func<IDbConnection>> connectionFactory = new()
{
{ Database.Red, () => new SqlConnection(Configuration.GetConnectionString("RedDatabase")) },
{ Database.Blue, () => new SqlConnection(Configuration.GetConnectionString("BlueDatabase")) }
};
services.AddSingleton(connectionFactory);
在您可以像这样获得实例 od 对对象构造函数的依赖之后:
public class ObjectQueries
{
private readonly IDbConnection _redConnection;
private readonly IDbConnection _blueConnection;
public ObjectQueries(Dictionary<Database, Func<IDbConnection>> connectionFactory)
{
_redConnection = connectionFactory[Database.Red]();
_blueConnection = connectionFactory[Database.Blue]();
}
}
感谢@ Stefan Steiger的想法;)
虽然开箱即用的实现不提供它,但这里有一个示例项目,允许您注册命名实例,然后将 INamedServiceFactory 注入您的代码并按名称提取实例。 与此处的其他工厂解决方案不同,它将允许您注册相同实现但配置不同的多个实例
服务的服务怎么样?
如果我们有一个 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 块是如此bleh ,而且,您将像编写工厂一样经常编写 Func。 从简单的、可重用的、使用更少的代码开始,如果结果不适合你,那就变得复杂。
我遇到了同样的问题,我使用了一个简单的扩展来允许命名服务。 你可以在这里找到它:
它允许您添加任意数量的(命名的)服务,如下所示:
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
}
希望能帮助到你
这个怎么样? 您可能可以更进一步,使用路由而不是枚举来解析服务类型。
输入到父类的接口,其中BalanceSheetReportDto : ReportDto
public interface IReportService<T> where T : ReportDto
{
Task<byte[]> GetFileStream(T reportDto);
}
实现它的抽象类。
public abstract class ReportService : IReportService<ReportDto>
{
public abstract Task<byte[]> GetFileStream(ReportDto reportDto);
}
这个抽象类是我们解析具体类型所需要的,因为您将无法将解析器类型指定为IReportService<ReportDto>
并返回实现BalaceSheetReportService
。 查看下一个代码块。
DI 的服务解析器。
public delegate ReportService ServiceResolver(ReportType key);
public static IServiceCollection AddReportServices(this IServiceCollection services)
{
services.AddScoped<BalanceSheetReportService>();
services.AddScoped<ServiceResolver>(serviceProvider => key =>
{
switch (key)
{
case ReportType.BalanceSheet:
return serviceProvider.GetService<BalanceSheetReportService>();
default:
throw new KeyNotFoundException();
}
});
并在控制器中添加解析器,但不需要强制转换为特定类型。
public class FinancialReportsController : BaseController
{
private ServiceCollectionExtension.ServiceResolver _resolver;
...
[HttpPost("balance-sheet")]
public async Task<byte[]> GetBalanceSheetReport([FromBody] BalanceSheetReportDto request)
{
try
{
var reportService = _resolver(ReportType.BalanceSheet); //magic
var data = await reportService.GetFileStream(request);
具体实施。
public class BalanceSheetReportService: ReportService
{
...
public override async Task<byte[]> GetFileStream(ReportDto reportDto)
{
return await GetFileStream((BalanceSheetReportDto) reportDto);
}
private async Task<byte[]> GetFileStream(BalanceSheetReportDto reportDto)
{
不相关,但您可以将其他服务(例如数据类)注入抽象类。
private MongoDbContext _context;
public ReportService(MongoDbContext context) {
_context = context;
}
然后在你的子类中调用这个构造函数并完成它。
public BalanceSheetReportService(MongoDbContext context) : base(context) {}
我真的很晚才参加聚会,但这非常容易解决,没有任何工厂模式或复杂的问题......
我遇到了这个问题并想出了一个超级简单的方法。 您只需要一个容器来放入您的对象,然后注册该容器。
所以假设你有这个(这是完全可重用的):
public class DependencyRegistration<TScope, TDependency>
{
public TDependency Dependency { get; }
public DependencyRegistration(TDependency dependency)
{
Dependency = dependency;
}
}
然后您可以“独立”注册您的服务:
.AddSingleton(serviceProvider =>
{
return new DependencyRegistration<IWidgetRepository, string>("the connection string");
})
.AddSingleton(serviceProvider =>
{
return new DependencyRegistration<IRestRequest, string>("the URL");
})
由于 TScope 有效地取代了命名注册,因此您只需使用它们:
[Inject]
public DependencyRegistration<IWidgetRepository, string> widgetConnectionStringRegistration { get; set; }
private string widgetConnectionString => widgetConnectionStringRegistration.Dependency;
因此,完全没有违反原则,您所需要的只是用于范围的唯一类型 - 如果您想在语义上精确,请使用任何有意义的或滚动您自己的定义:
public class TokenContext
{
public interface IAdministrationToken { }
public interface IUserToken { }
}
所以:
.AddSingleton(serviceProvider =>
{
return new DependencyRegistration<TokenContext.IUserToken, string>("the user token");
})
.AddSingleton(serviceProvider =>
{
return new DependencyRegistration<TokenContext.IAdministrationToken, string>("the admin token");
})
而不是“GetRequiredService”扩展方法,我创建了一个“GetRequiredRegisteredService”版本,以更轻松地解决相互依赖的依赖关系:
.AddSingleton(serviceProvider =>
{
var myURL = serviceProvider.GetRequiredRegisteredService<IRestRequest, string>();
return new new RestRequest(myURL);
})
一旦你看到修复是多么容易,它就会超级容易使用。
哦,甚至超级容易安装:
Install-Package CodeRed.Extensions.DependencyInjection.DependencyRegister
享受!
保罗
大多数答案要么提出具有硬编码实例化的工厂方法,要么要求提前实例化所有命名实例。 我想要一个通用解决方案而不实例化所有命名服务。 这是我的解决方案:
用法:
public MyConsumer(NamedServiceFactory<IMyService> factory) {
IMyService service = factory.Create("foo");
}
DI注册:
host.ConfigureServices(services => {
services.AddNamed<IMyService, MyServiceFoo>("foo");
services.AddNamed<IMyService, MyServiceBar>("bar");
});
执行:
namespace Microsoft.Extensions.DependencyInjection
{
public static class NamedServiceFactoryExtensions
{
public static IServiceCollection AddNamed<T>(this IServiceCollection services, string? name, Func<IServiceProvider, T> factory) where T : class
{
if (!services.Contains<NamedServiceFactory<T>>())
services.AddSingleton<NamedServiceFactory<T>>();
services.AddSingleton<NamedFactory<T>>(sp => new NamedFactory<T>(name, factory));
return services;
}
public static IServiceCollection AddNamed<T, TImpl>(this IServiceCollection services, string? name) where T : class where TImpl : T
=> AddNamed<T>(services, name, sp => sp.GetRequiredService<TImpl>());
}
}
public class NamedFactory<T>
{
public string Name { get; private set; }
private readonly Func<IServiceProvider, T> Factory;
public NamedFactory(string? name, Func<IServiceProvider, T> factory)
{
this.Factory = factory;
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
Name = name!;
}
public T Create(IServiceProvider serviceProvider) => Factory(serviceProvider);
}
public class NamedServiceFactory<T>
{
private Dictionary<string, NamedFactory<T>> Factories = new();
private readonly IServiceProvider ServiceProvider;
public NamedServiceFactory(IEnumerable<NamedFactory<T>> factories, IServiceProvider serviceProvider)
{
this.ServiceProvider = serviceProvider;
foreach (var factory in factories)
Factories.Add(factory.Name, factory);
}
public T Create(string? name, IServiceProvider? serviceProvider = null)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
if (!Factories.TryGetValue(name!, out var factory))
throw new InvalidOperationException($"Service not found with name {name}");
return factory.Create(serviceProvider ?? ServiceProvider);
}
}
随意添加接口INamedServiceFactory<T>
和INamedFactory<T>
。
在我的解决方案中,我在 controller 操作上使用属性而不是命名服务注册。 通过这种方式,我选择使用操作请求的 DI scope 中的服务实现之一。
例如,如果我为报告端点指定了服务实现,我的代码如下所示:
public class HomeController : ControllerBase
{
private readonly IService _service;
public HomeController(IService service)
{
_service = service;
}
[HttpGet]
[ReportScope]
public IEnumerable<WeatherForecast> GetReport()
{
//Use Service1 implementation, because of ReportScope attribute
_service.DoSomeThing();
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
//Use Service2 implementation
_service.DoSomeThing();
}
该属性由自定义中间件处理:
public class ReportScopeLoggingMiddleware
{
private readonly RequestDelegate _next;
public ReportScopeLoggingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, ReportScopeContext scopeContext)
{
var controllerActionDescriptor = context
.GetEndpoint()
.Metadata
.GetMetadata<ControllerActionDescriptor>();
bool analytical = controllerActionDescriptor.EndpointMetadata.Any(m => m is ReportScopeAttribute);
if (analytical) scopeContext.SetActive();
await _next(context);
}
}
在这个中间件中,我使用ReportScopeContext
。
public class ReportScopeContext
{
public bool Active { get; private set; } = false;
public void SetActive()
{
Active = true;
}
}
此ReportScopeContext
在 DI 中具有范围生命周期,我将其用于 select 的 IService 实现:
services.AddScoped<ReportScopeContext>();
services.AddTransient<Service2>();
services.AddTransient<Service1>();
services.AddTransient<IService>(sp =>
sp.GetRequiredService<ReportScopeContext>().Active
? sp.GetRequiredService<Service1>()
: sp.GetRequiredService<Service2>());
FooA
、 FooB
和FooC
实现IFoo
服务提供商:
services.AddTransient<FooA>(); // Note that there is no interface
services.AddTransient<FooB>();
services.AddTransient<FooC>();
services.AddSingleton<Func<Type, IFoo>>(x => type =>
{
return (IFoo)x.GetService(type);
});
目的地:
public class Test
{
private readonly IFoo foo;
public Test(Func<Type, IFoo> fooFactory)
{
foo = fooFactory(typeof(FooA));
}
....
}
如果您想将FooAMock
更改为FooA
以进行测试:
services.AddTransient<FooAMock>();
services.AddSingleton<Func<Type, IFoo>>(x => type =>
{
if(type.Equals(typeof(FooA))
return (IFoo)x.GetService(typeof(FooAMock));
return null;
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.