C# 如何在Asp.NETCore中注册同一接口的多个实现?
我有从同一接口派生的服务C# 如何在Asp.NETCore中注册同一接口的多个实现?,c#,asp.net-core,asp.net-core-mvc,coreclr,C#,Asp.net Core,Asp.net Core Mvc,Coreclr,我有从同一接口派生的服务 public interface IService { } public class ServiceA : IService { } public class ServiceB : IService { } public class ServiceC : IService { } 通常,其他IoC容器(如Unity)允许您通过一些区分它们的键注册具体实现 在ASP.NET Core中,如何注册这些服务并在运行时基于某个密钥解析它们 我没有看到任何Add服务方法采用k
public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { }
public class ServiceC : IService { }
通常,其他IoC容器(如Unity
)允许您通过一些区分它们的键注册具体实现
在ASP.NET Core中,如何注册这些服务并在运行时基于某个密钥解析它们
我没有看到任何Add
服务方法采用key
或name
参数,这些参数通常用于区分具体实现
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?
}
}
public class BalanceSheetReportService: ReportService
{
...
public override async Task<byte[]> GetFileStream(ReportDto reportDto)
{
return await GetFileStream((BalanceSheetReportDto) reportDto);
}
private async Task<byte[]> GetFileStream(BalanceSheetReportDto reportDto)
{
工厂模式是这里唯一的选择吗
更新1
我已经阅读了这篇文章,它展示了当我们有多个具体实现时如何使用工厂模式来获取服务实例。然而,这仍然不是一个完整的解决方案。调用\u 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;
}
}
\u serviceProvider.GetService()
如何注入适当的连接字符串?
在Unity或任何其他IoC库中,我们都可以在类型注册时进行此操作。我可以使用,但是,这将需要我注入所有设置。我无法将特定的连接字符串注入服务
还请注意,我试图避免使用其他容器(包括Unity),因为这样我就必须将其他所有内容(例如控制器)也注册到新容器中
此外,使用工厂模式创建服务实例也不利于DIP,因为它增加了客户机的依赖性数量
因此,我认为ASP.NET核心中的默认DI缺少两个方面:
使用密钥注册实例的能力
在注册期间将静态数据注入构造函数的能力
您是对的,内置ASP.NET核心容器没有注册多个服务然后检索特定服务的概念,正如您所建议的,工厂是这种情况下唯一真正的解决方案
或者,您可以切换到第三方容器,如Unity或StructureMap,它提供您需要的解决方案(此处有文档:)。Microsoft.Extensions.DependencyInjection不支持它
但是,您可以插入另一种依赖项注入机制,如StructureMap
,并且它非常有用
一点也不难:
在项目中向StructureMap添加依赖项。json
:
"Structuremap.Microsoft.DependencyInjection" : "1.0.1",
将其注入ConfigureServices
中的ASP.NET管道,并注册您的类
就这样
对于要构建的示例,您需要
public interface IPet
{
string Name { get; set; }
}
public class Cat : IPet
{
public Cat(string name)
{
Name = name;
}
public string Name {get; set; }
}
我也遇到过同样的问题,我想和大家分享我是如何解决这个问题的,以及为什么
正如你提到的,有两个问题第一个:
在Asp.NETCore中,如何注册这些服务并在
基于某个密钥的运行时
那么我们有什么选择呢?人们建议两个:
- 使用自定义工厂(如
\u myFactory.GetServiceByKey(key)
)
- 使用另一个DI引擎(如
\u unityContainer.Resolve(key)
)
工厂模式是这里唯一的选择吗
事实上,这两个选项都是工厂,因为每个IoC容器也是一个工厂(高度可配置且复杂)。在我看来,其他选择也是工厂模式的变体
那么,什么选择更好呢?在这里,我同意@Sock的建议,他建议使用定制工厂,这就是为什么
首先,我总是尽量避免在不需要的时候添加新的依赖项。所以我同意你的观点。此外,使用两个DI框架比创建自定义工厂抽象更糟糕。在第二种情况下,您必须添加新的包依赖项(如Unity),但依赖新的工厂接口在这里就不那么糟糕了。我相信ASP.NET核心DI的主要思想是简单。它维护以下一组最小的功能。如果您需要一些额外的功能,那么DIY或使用实现所需功能的相应功能(打开-关闭原则)
其次,我们通常需要为单个服务注入许多命名依赖项。在统一的情况下,您可能必须指定构造函数参数的名称(使用InjectionConstructor
)。此注册使用反射和一些智能逻辑来猜测构造函数的参数。如果注册与构造函数参数不匹配,这也可能导致运行时错误。另一方面,当使用自己的工厂时,您可以完全控制如何提供构造函数参数。它更具可读性,并且在编译时解析。再说一遍
第二个问题:
_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>())));
var mongoConnection=/。。。
变量efConnection=/。。。
var otherConnection=/。。。
services.AddTransient(
s=>新MyFactoryImpl(
mongoConnection、efConnection、otherConnection、,
s、 GetService(),s.GetService());
当我发现自己在t
var mongoConnection = //...
var efConnection = //...
var otherConnection = //...
services.AddTransient<IMyFactory>(
s => new MyFactoryImpl(
mongoConnection, efConnection, otherConnection,
s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));
public delegate IService ServiceResolver(string key);
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
}
});
public class Consumer
{
private readonly IService _aService;
public Consumer(ServiceResolver serviceAccessor)
{
_aService = serviceAccessor("A");
}
public void UseServiceA()
{
_aService.DoTheThing();
}
}
services.AddSingleton<IService, ServiceA>();
services.AddSingleton<IService, ServiceB>();
services.AddSingleton<IService, ServiceC>();
var services = serviceProvider.GetServices<IService>();
var serviceB = services.First(o => o.GetType() == typeof(ServiceB));
var serviceZ = services.First(o => o.Name.Equals("Z"));
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);
public class SNSConfig
{
public string SNSDefaultRegion { get; set; }
public string SNSSMSRegion { get; set; }
}
"SNSRegions": "ap-south-1,us-west-2",
"SNSDefaultRegion": "ap-south-1",
"SNSSMSRegion": "us-west-2",
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();
}
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();
}
.........
}
public interface IService
{
}
public interface IServiceA: IService
{}
public interface IServiceB: IService
{}
public 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>();
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");
/// <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;
}
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();
.....
}
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);
}
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"; } }
}
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();
}
...
}
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;
}
services.AddTransient<IMyInterface<CustomerSavedConsumer>, CustomerSavedConsumer>();
services.AddTransient<IMyInterface<ManagerSavedConsumer>, ManagerSavedConsumer>();
public interface IMyInterface<T> where T : class, IMyInterface<T>
{
Task Consume();
}
public class CustomerSavedConsumer: IMyInterface<CustomerSavedConsumer>
{
public async Task Consume();
}
public class ManagerSavedConsumer: IMyInterface<ManagerSavedConsumer>
{
public async Task Consume();
}
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
IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider)["logDB"];
logDB.Connection
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;
};
});
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....
}
// 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();
}
}
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);
}
}
}
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;
}
/// <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");
/// <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);
public MyController(NamedServiceResolver<AnimalService> namedServices)
{
AnimalService serviceA = namedServices["A"];
AnimalService serviceB = namedServices["B"]; // instance of BearService returned derives from AnimalService
}
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
}
{
"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>();
[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();
}
}
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;
}
public interface IService<T> where T : class {}
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));
}
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);
}
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 ServiceDecisionEngine<SomeData>: IServiceDecisionEngine<T>
{
public ServiceDecisionEngine(IService serviceA, IService serviceB) { }
public IService ResolveService(SomeData dataNeededForLogic)
{
if (dataNeededForLogic.someValue == true)
{
return serviceA;
}
return serviceB;
}
}
public enum Database
{
Red,
Blue
}
Dictionary<Database, Func<IDbConnection>> connectionFactory = new()
{
{ Database.Red, () => new SqlConnection(Configuration.GetConnectionString("RedDatabase")) },
{ Database.Blue, () => new SqlConnection(Configuration.GetConnectionString("BlueDatabase")) }
};
services.AddSingleton(connectionFactory);
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]();
}
}
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 { ... }
services.AddScoped<ITodoService, TodoService>();
services.AddScoped<IValuesService, ValuesService>();
public class UsageClass {
public UsageClass(ITodoService todoService, IValuesService valuesService) {}
}
namespace MultipleImplementation
{
public interface IShoppingCart
{
object GetCart();
}
}
namespace MultipleImplementation
{
public class ShoppingCartCache : IShoppingCart
{
public object GetCart()
{
return "Cart loaded from cache.";
}
}
}
namespace MultipleImplementation
{
public class ShoppingCartDB : IShoppingCart
{
public object GetCart()
{
return "Cart loaded from DB";
}
}
}
namespace MultipleImplementation
{
public class ShoppingCartAPI : IShoppingCart
{
public object GetCart()
{
return "Cart loaded through API.";
}
}
}
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();
}
}
}
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();
}
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 container.GetInstance<IServiceProvider>();
}
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();
});