[英]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.