LP1*_*P13 183 c# asp.net-core-mvc coreclr asp.net-core
我有来自相同接口的服务
public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { }
public class ServiceC : IService { }
Run Code Online (Sandbox Code Playgroud)
通常,其他IOC容器Unity允许您通过某些Key区分它们来注册具体实现.
在Asp.Net Core中,我如何注册这些服务并在运行时根据某些键解决它?
我没有看到任何通常用于区分具体实现的AddService方法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
}
}
Run Code Online (Sandbox Code Playgroud)
工厂模式是唯一的选择吗?
Update1
我已经阅读了这里的文章,该文章展示了当我们有多个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;
}
}
Run Code Online (Sandbox Code Playgroud)
如何_serviceProvider.GetService()注入适当的连接字符串?在Unity或任何其他IOC中,我们可以在类型注册时执行此操作.我可以使用IOption但是需要我注入所有设置,我不能将特定的连接字符串注入服务.
另请注意,我正在尝试避免使用其他容器(包括Unity),因为我必须使用新容器注册其他所有内容(例如控制器).
同时使用工厂模式来创建服务实例是针对DIP的,因为工厂增加了客户端被迫依赖于此处的详细信息的依赖项数量
所以我认为ASP.NET核心中的默认DI缺少2个东西
1>使用密钥
2 注册实例>在注册期间将静态数据注入构造函数
Mig*_*lla 186
Func当我发现自己处于这种情况时,我做了一个简单的解决方法.
public delegate IService ServiceResolver(string key);
Run Code Online (Sandbox Code Playgroud)
并在DI注册的任何类中使用它,如:
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
}
});
Run Code Online (Sandbox Code Playgroud)
UPDATE
请记住,在这个例子中,解析的关键是一个字符串,为了简单起见,因为OP特别要求这个案例.
但是你可以使用任何自定义分辨率类型作为键,因为你通常不希望一个巨大的n-case开关腐烂你的代码.取决于您的应用如何扩展.
rnr*_*ies 65
另一种选择是使用扩展方法GetServices的Microsoft.Extensions.DependencyInjection.
将您的服务注册为:
services.AddSingleton<IService, ServiceA>();
services.AddSingleton<IService, ServiceB>();
services.AddSingleton<IService, ServiceC>();
Run Code Online (Sandbox Code Playgroud)
然后用一点Linq解决:
var services = serviceProvider.GetServices<IService>();
var serviceB = services.First(o => o.GetType() == typeof(ServiceB));
Run Code Online (Sandbox Code Playgroud)
要么
var serviceZ = services.First(o => o.Name.Equals("Z"));
Run Code Online (Sandbox Code Playgroud)
(假设IService有一个名为"Name"的字符串属性)
一定要有 using Microsoft.Extensions.DependencyInjection;
AspNet 2.1来源: GetServices
小智 39
工厂方法当然是可行的。另一种方法是使用继承来创建从 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
{}
Run Code Online (Sandbox Code Playgroud)
容器:
container.Register<IServiceA, ServiceA>();
container.Register<IServiceB, ServiceB>();
container.Register<IServiceC, ServiceC>();
Run Code Online (Sandbox Code Playgroud)
Ric*_*ter 21
这里的大多数答案都违反了单一职责原则(服务类不应自行解决依赖关系)和/或使用服务定位器反模式。
避免这些问题的另一种选择是:
我写了一篇更详细的文章:Dependency Injection in .NET: A way to work around missing named registrations
Gra*_*ray 18
这个派对有点晚了,但这是我的解决方案:...
Startup.cs 或 Program.cs 如果通用处理程序...
services.AddTransient<IMyInterface<CustomerSavedConsumer>, CustomerSavedConsumer>();
services.AddTransient<IMyInterface<ManagerSavedConsumer>, ManagerSavedConsumer>();
Run Code Online (Sandbox Code Playgroud)
T 接口设置的 IMyInterface
public interface IMyInterface<T> where T : class, IMyInterface<T>
{
Task Consume();
}
Run Code Online (Sandbox Code Playgroud)
T的IMyInterface的具体实现
public class CustomerSavedConsumer: IMyInterface<CustomerSavedConsumer>
{
public async Task Consume();
}
public class ManagerSavedConsumer: IMyInterface<ManagerSavedConsumer>
{
public async Task Consume();
}
Run Code Online (Sandbox Code Playgroud)
访问控制器中的服务
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;
}
}
Run Code Online (Sandbox Code Playgroud)
希望如果这样做有任何问题,有人会善意地指出为什么这是错误的做法。
Ger*_*oli 16
它不受支持Microsoft.Extensions.DependencyInjection.
但是你可以插入另一个依赖注入机制,比如StructureMap See it's Home page和它的GitHub Project.
这根本不难:
在您的project.json:中向StructureMap添加依赖项:
"Structuremap.Microsoft.DependencyInjection" : "1.0.1",
Run Code Online (Sandbox Code Playgroud)将它注入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>();
}
Run Code Online (Sandbox Code Playgroud)然后,要获取命名实例,您需要请求 IContainer
public class HomeController : Controller
{
public HomeController(IContainer injectedContainer)
{
var myPet = injectedContainer.GetInstance<IPet>("B");
string name = myPet.Name; // Returns "CatB"
Run Code Online (Sandbox Code Playgroud)而已.
对于要构建的示例,您需要
public interface IPet
{
string Name { get; set; }
}
public class Cat : IPet
{
public Cat(string name)
{
Name = name;
}
public string Name {get; set; }
}
Run Code Online (Sandbox Code Playgroud)
Ste*_*ger 12
死灵法术。
我认为这里的人们正在重新发明轮子 - 很糟糕,如果我可以这么说......
如果你想通过键注册一个组件,只需使用字典:
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"));
Run Code Online (Sandbox Code Playgroud)
然后在服务集合中注册字典:
services.AddSingleton<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(dict);
Run Code Online (Sandbox Code Playgroud)
如果您不愿意获取字典并通过键访问它,您可以通过向服务集合添加一个额外的键查找方法来隐藏字典:(
使用委托/闭包应该给潜在的维护者一个机会了解发生了什么 - 箭头符号有点神秘)
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
};
});
Run Code Online (Sandbox Code Playgroud)
现在您可以使用以下任一方式访问您的类型
IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<Func<string, IConnectionFactory>>(serviceProvider)("LogDB");
logDB.Connection
Run Code Online (Sandbox Code Playgroud)
或者
System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider);
dbs["logDB"].Connection
Run Code Online (Sandbox Code Playgroud)
正如我们所看到的,第一个完全是多余的,因为你也可以用字典来做到这一点,而不需要闭包和 AddTransient(如果你使用 VB,甚至大括号也不会有不同):
IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider)["logDB"];
logDB.Connection
Run Code Online (Sandbox Code Playgroud)
(越简单越好 - 不过您可能想将其用作扩展方法)
当然,如果你不喜欢字典,你也可以为你的界面配备一个属性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"));
// /sf/ask/2742249261/
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;
};
});
Run Code Online (Sandbox Code Playgroud)
但这需要更改您的界面以适应该属性,并且循环遍历大量元素应该比关联数组查找(字典)慢得多。
不过,很高兴知道它可以在没有字典的情况下完成。
这些只是我的 0.05 美元
Zac*_*ins 12
以下是有关如何创建依赖关系解析器的示例,该依赖关系解析器允许您指定通用参数来解析依赖关系。
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());
Run Code Online (Sandbox Code Playgroud)
public interface IDependencyResolver<out TResolve, in TArg>
{
TResolve GetDependency(TArg arg);
}
Run Code Online (Sandbox Code Playgroud)
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}");
}
}
Run Code Online (Sandbox Code Playgroud)
Mus*_*tor 12
通过 .NET 8,我们获得了扩展库的更新,它带来了键控 DI 服务,而这正是您所需要的。
这里引用了 Microsoft 文档:What's new in .NET 8? - 密钥 DI 服务:
键控依赖注入 (DI) 服务提供了一种使用密钥注册和检索 DI 服务的方法。通过使用密钥,您可以确定注册和使用服务的方式。以下是一些新的 API:
以下示例向您展示如何使用密钥 DI 服务。
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<BigCacheConsumer>();
builder.Services.Addsingleton<SmallCacheConsumer>();
builder.Services.AddKeyedSingleton<IMemoryCache, BigCache>("big");
builder.Services.AddKeyedSingleton<IMemoryCache, SmallCache>("small");
var app = builder.Build();
app.MapGet("/big", (BigCacheConsumer data) => data.GetData());
app.MapGet("/small", (SmallCacheConsumer data) => data.GetData());
app.Run();
class BigCacheConsumer([FromKeyedServices("big")] IMemoryCache cache)
{
public object? GetData() => cache.Get("data");
}
class SmallCacheConsumer(IKeyedServiceProvider keyedServiceProvider)
{
public object? GetData() => keyedServiceProvider.GetRequiredKeyedService<IMemoryCache>("small");
}
Run Code Online (Sandbox Code Playgroud)
有关更多信息,请参阅dotnet/runtime#64427。
nel*_*eus 11
我遇到了同样的问题,想分享我是如何解决它的原因.
如你所说,有两个问题.首先:
在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>())));
Run Code Online (Sandbox Code Playgroud)
Soc*_*ock 10
你是对的,内置的ASP.NET Core容器没有注册多个服务然后检索特定服务的概念,正如你的建议,工厂是这种情况下唯一真正的解决方案.
或者,您可以切换到Unity或StructureMap之类的第三方容器,它可以提供您需要的解决方案(请在此处记录:https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing- -default-services-container).
Jac*_*rts 10
我没有时间通读所有内容,但似乎每个人都在为本来不应该存在的问题提供解决方案。
如果您需要所有已注册的 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;
}
}
Run Code Online (Sandbox Code Playgroud)
现在在您的 DI 中,您可以这样做,.AddScoped<IServiceDecisionEngine<SomeData>, new ServiceDecisionEngine(new ServiceA(), new ServiceB())需要 IService 的 managerService 将通过注入和使用 IServiceDecisionEngine 来获取它。
Art*_*nig 10
我发现的关于多个实现的最佳文档/教程来自以下来源:
.NET Core 依赖注入 - 一个接口,多个实现,(由 Akshay Patel 撰写)
教程中提到的示例遵循控制器/服务/存储库约定,并具有Func实现在 Startup.cs 的 ConfigurationService() 中实例化正确/需要的接口实现。教程是我发现澄清这个问题的最佳方法。
下面是上述文章的粗略复制/粘贴:(示例涉及购物车接口的 3 种不同实现,一种方法使用缓存解决方案,另一种方法使用 API,其他方法使用数据库实现。)
要多重实现的接口。 ..
namespace MultipleImplementation
{
public interface IShoppingCart
{
object GetCart();
}
}
Run Code Online (Sandbox Code Playgroud)
实施方案A
namespace MultipleImplementation
{
public class ShoppingCartCache : IShoppingCart
{
public object GetCart()
{
return "Cart loaded from cache.";
}
}
}
Run Code Online (Sandbox Code Playgroud)
实施B
namespace MultipleImplementation
{
public class ShoppingCartDB : IShoppingCart
{
public object GetCart()
{
return "Cart loaded from DB";
}
}
}
Run Code Online (Sandbox Code Playgroud)
实施C
namespace MultipleImplementation
{
public class ShoppingCartAPI : IShoppingCart
{
public object GetCart()
{
return "Cart loaded through API.";
}
}
}
Run Code Online (Sandbox Code Playgroud)
将使用存储库中的接口声明来选择 A、B、C......
namespace MultipleImplementation
{
public interface IShoppingCartRepository
{
object GetCart();
}
}
Run Code Online (Sandbox Code Playgroud)
枚举来选择将使用哪个实现...
namespace MultipleImplementation
{
public class Constants
{
}
public enum CartSource
{
Cache=1,
DB=2,
API=3
}
}
Run Code Online (Sandbox Code Playgroud)
声明的存储库接口的实现(谁将选择哪个实现......)
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
最后,将所有内容打包到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();
}
Run Code Online (Sandbox Code Playgroud)
在此,我强调,6 分钟的阅读时间将让你理清思绪,帮助你解决在一个接口中实现多个实现的问题。祝你好运!
自从我上面的帖子,我已经转移到一个通用工厂类
用法
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);
}
Run Code Online (Sandbox Code Playgroud)
执行
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]);
}
}
Run Code Online (Sandbox Code Playgroud)
延期
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
显然,您只需注入服务接口的 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);
Run Code Online (Sandbox Code Playgroud)
配置文件
public class SNSConfig
{
public string SNSDefaultRegion { get; set; }
public string SNSSMSRegion { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
appsettings.json
"SNSRegions": "ap-south-1,us-west-2",
"SNSDefaultRegion": "ap-south-1",
"SNSSMSRegion": "us-west-2",
Run Code Online (Sandbox Code Playgroud)
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();
}
Run Code Online (Sandbox Code Playgroud)
现在您可以在您的自定义服务或控制器中获取您想要的区域的 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();
}
.........
}
Run Code Online (Sandbox Code Playgroud)
为什么不使用继承?通过这种方式,我们可以拥有任意数量的界面副本,并且可以为每个副本选择合适的名称。我们有类型安全的好处
public interface IReportGenerator
public interface IExcelReportGenerator : IReportGenerator
public interface IPdfReportGenerator : IReportGenerator
Run Code Online (Sandbox Code Playgroud)
具体类:
public class ExcelReportGenerator : IExcelReportGenerator
public class PdfReportGenerator : IPdfReportGenerator
Run Code Online (Sandbox Code Playgroud)
登记:
代替
services.AddScoped<IReportGenerator, PdfReportGenerator>();
services.AddScoped<IReportGenerator, ExcelReportGenerator>();
Run Code Online (Sandbox Code Playgroud)
我们有 :
services.AddScoped<IPdfReportGenerator, PdfReportGenerator>();
services.AddScoped<IExcelReportGenerator, ExcelReportGenerator>();
Run Code Online (Sandbox Code Playgroud)
客户:
public class ReportManager : IReportManager
{
private readonly IExcelReportGenerator excelReportGenerator;
private readonly IPdfReportGenerator pdfReportGenerator;
public ReportManager(IExcelReportGenerator excelReportGenerator,
IPdfReportGenerator pdfReportGenerator)
{
this.excelReportGenerator = excelReportGenerator;
this.pdfReportGenerator = pdfReportGenerator;
}
Run Code Online (Sandbox Code Playgroud)
这种方法也允许虱子耦合代码,因为我们可以将 IReportGenerator 移动到应用程序的核心,并具有将在更高级别声明的子接口。
我只是简单地注入一个IEnumerable
Startup.cs中的ConfigureServices
Assembly.GetEntryAssembly().GetTypesAssignableFrom<IService>().ForEach((t)=>
{
services.AddScoped(typeof(IService), t);
});
Run Code Online (Sandbox Code Playgroud)
服务文件夹
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"; } }
}
Run Code Online (Sandbox Code Playgroud)
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();
}
...
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
我认为以下文章“ Resoluci\xc3\xb3n din\xc3\xa1mica de tipos en tiempo de ejecuci\xc3\xb3n en el contenedor de IoC de .NET Core ”中描述的解决方案更简单,并且不需要工厂。
\n您可以使用通用接口
\npublic interface IService<T> where T : class {}\nRun Code Online (Sandbox Code Playgroud)\n然后在 IoC 容器上注册所需的类型:
\nservices.AddTransient<IService<ServiceA>, ServiceA>();\nservices.AddTransient<IService<ServiceB>, ServiceB>();\nRun Code Online (Sandbox Code Playgroud)\n之后,您必须声明依赖项,如下所示:
\nprivate readonly IService<ServiceA> _serviceA;\nprivate readonly IService<ServiceB> _serviceB;\n\npublic WindowManager(IService<ServiceA> serviceA, IService<ServiceB> serviceB)\n{\n this._serviceA = serviceA ?? throw new ArgumentNullException(nameof(serviceA));\n this._serviceB = serviceB ?? throw new ArgumentNullException(nameof(ServiceB));\n}\nRun Code Online (Sandbox Code Playgroud)\n
答案很晚,但这就是我这样做的方式,它比这个问题的其他一些解决方案有一些优势。
优点:
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);
Run Code Online (Sandbox Code Playgroud)
用法示例:
public ExampleClass(Func<string, IService> keyedServiceFactory, StringKeyedService<IService> keyedServiceDelegate)
{
var serviceKey = Configuration.GetValue<string>("IService.Key");
var service = keyedServiceFactory(serviceKey);
var serviceC = keyedServiceDelegate("C");
}
Run Code Online (Sandbox Code Playgroud)
执行:
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
也可以在此基础上制作一个流体界面,如果有人对此感兴趣,请告诉我。
液体使用示例:
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();
});
Run Code Online (Sandbox Code Playgroud)
小智 6
我有同样的问题,我解决了使用<T>
我的界面:
public interface IProvider<T>
{
Task<string> GetTotalSearchResults(string searchValue);
}
Run Code Online (Sandbox Code Playgroud)
我的服务配置:
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>();
});
Run Code Online (Sandbox Code Playgroud)
您可以在课堂上使用它:
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;
}
Run Code Online (Sandbox Code Playgroud)
小智 5
我的解决方案的价值......考虑切换到温莎城堡,因为不能说我喜欢上面的任何解决方案。对不起!!
public interface IStage<out T> : IStage { }
public interface IStage {
void DoSomething();
}
Run Code Online (Sandbox Code Playgroud)
创建您的各种实现
public class YourClassA : IStage<YouClassA> {
public void DoSomething()
{
...TODO
}
}
public class YourClassB : IStage<YourClassB> { .....etc. }
Run Code Online (Sandbox Code Playgroud)
登记
services.AddTransient<IStage<YourClassA>, YourClassA>()
services.AddTransient<IStage<YourClassB>, YourClassB>()
Run Code Online (Sandbox Code Playgroud)
构造函数和实例用法...
public class Whatever
{
private IStage ClassA { get; }
public Whatever(IStage<YourClassA> yourClassA)
{
ClassA = yourClassA;
}
public void SomeWhateverMethod()
{
ClassA.DoSomething();
.....
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
87891 次 |
| 最近记录: |