如何在Asp中注册同一个接口的多个实现。网络核心?

我拥有从同一接口派生的服务。

public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { }
public class ServiceC : IService { }

通常,像Unity这样的其他IoC容器允许你通过一些Key来注册具体的实现。

在ASP。NET Core,我如何注册这些服务,并在运行时根据一些关键字解析它们?

我没有看到任何带有keyname参数的Add服务方法,这些参数通常用于区分具体实现。

    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?
}
}

工厂模式是这里唯一的选择吗?

< p > Update1 < br > 我已经阅读了文章在这里,它展示了当我们有多个具体实现时如何使用工厂模式来获得服务实例。然而,这仍然不是一个完整的解决方案。当我调用_serviceProvider.GetService()方法时,我不能将数据注入构造函数。< / p >

举个例子:

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的,因为它增加了客户端具有细节的依赖项的数量。

所以,我认为默认DI在ASP。NET Core缺少两样东西:

  1. 使用键注册实例的能力
  2. 在注册期间向构造函数注入静态数据的能力
343968 次浏览

你说得对,内置的ASP。NET Core容器没有注册多个服务然后检索特定服务的概念,正如您所建议的,工厂是这种情况下唯一真正的解决方案。

或者,你可以切换到第三方容器,如Unity或StructureMap,它提供了你需要的解决方案(文档在这里:https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing-the-default-services-container)。

Microsoft.Extensions.DependencyInjection不支持。

但是你可以插入另一个依赖注入机制,比如StructureMap 看它的主页和它的GitHub项目

这一点都不难:

  1. 在你的project.json中添加一个StructureMap依赖项:

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
    
  2. Inject it into the ASP.NET pipeline inside ConfigureServices and register your classes (see docs)

    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>();
    }
    
  3. Then, to get a named instance, you will need to request the IContainer

    public class HomeController : Controller
    {
    public HomeController(IContainer injectedContainer)
    {
    var myPet = injectedContainer.GetInstance<IPet>("B");
    string name = myPet.Name; // Returns "CatB"
    

That's it.

For the example to build, you need

    public interface IPet
{
string Name { get; set; }
}


public class Cat : IPet
{
public Cat(string name)
{
Name = name;
}


public string Name {get; set; }
}

我也遇到过同样的问题,我想分享一下我是如何解决它的,以及为什么。

正如你提到的,有两个问题。第一个:

< p >在Asp。Net Core我如何注册这些服务和解决它 基于某个键的运行时?< / p >

那么我们有什么选择呢?人们建议两种:

  • 使用自定义工厂(如_myFactory.GetServiceByKey(key))

  • 使用另一个DI引擎(如_unityContainer.Resolve<IService>(key))

工厂模式是这里唯一的选择吗?

事实上,这两个选项都是工厂,因为每个IoC容器也是一个工厂(高度可配置且复杂)。在我看来,其他选项也是工厂模式的变体。

那么哪种选择更好呢?在这里我同意@Sock的建议,他建议使用自定义工厂,这就是原因。

首先,我总是尽量避免在不真正需要的时候添加新的依赖项。所以在这一点上我同意你的看法。此外,使用两个DI框架比创建自定义工厂抽象更糟糕。在第二种情况下,你必须添加新的包依赖(如Unity),但依赖于一个新的工厂界面在这里不那么邪恶。ASP的主要思想。NET Core DI,我相信是简单的。它在KISS原则之后维持一个最小的特征集。如果你需要一些额外的功能,那么DIY或使用相应的Plungin来实现所需的功能(开闭原则)。

其次,我们经常需要为单个服务注入许多命名依赖项。在Unity的情况下,你可能必须为构造函数参数指定名称(使用InjectionConstructor)。这个注册使用反射和一些聪明的逻辑来猜测构造函数的参数。如果注册与构造函数参数不匹配,也可能导致运行时错误。另一方面,当使用自己的工厂时,您可以完全控制如何提供构造函数参数。它可读性更强,并且在编译时进行解析。KISS原则

第二个问题:

_serviceProvider.GetService()如何注入适当的连接 字符串?< / p >

首先,我同意你的观点,依赖于像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>())));

当我发现自己处于这种情况时,我使用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

显然,你可以注入你的服务接口的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; }
}

appsettings.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();
}


.........
}

工厂方法当然是可行的。另一种方法是使用继承来创建从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>();

虽然开箱即用的实现没有提供这种功能,但这里有一个示例项目,它允许您注册已命名的实例,然后将INamedServiceFactory注入到代码中,并按名称提取实例。与这里的其他工厂解决方案不同,它将允许你注册相同的实现的多个实例,但配置不同

https://github.com/macsux/DotNetDINamedInstances

虽然@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;
}

我的解决方案是…考虑转到温莎城堡,因为我不能说我喜欢上面的任何一个解决方案。对不起! !

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();
.....
}

服务的服务怎么样?

如果我们有一个INamedService接口(带有. name属性),我们可以为. getservice(字符串名称)编写一个IServiceCollection扩展,其中扩展将接受该字符串参数,并对其本身执行. getservices(),并在每个返回的实例中,找到其INamedService的实例。Name与给定的名称匹配。

是这样的:

public interface INamedService
{
string Name { get; }
}


public static T GetService<T>(this IServiceProvider provider, string serviceName)
where T : INamedService
{
var candidates = provider.GetServices<T>();
return candidates.FirstOrDefault(s => s.Name == serviceName);
}

因此,您的IMyService必须实现INamedService,但是您将得到您想要的基于密钥的解决方案,对吗?

公平地说,必须拥有这个INamedService接口似乎很难看,但如果您想更进一步,使事情变得更优雅,那么可以通过这个扩展中的代码找到实现/类上的[NamedServiceAttribute(“a”)],并且它也可以工作得很好。更公平地说,反射很慢,所以可能需要进行优化,但老实说,这是DI引擎应该帮助解决的问题。速度和简单性都是TCO的重要贡献者。

总而言之,不需要显式的工厂,因为“查找命名服务”是一个可重用的概念,而且工厂类不能作为解决方案进行扩展。Func<>看起来很好,但是开关块是在那里,同样,你会像写工厂一样频繁地写Funcs。从简单的、可重用的、代码更少的开始,如果这对你来说不适合,那么就变得复杂一些。

我只是注入了一个IEnumerable

在Startup.cs中配置services

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"; } }
}

MyController.cs

public class MyController
{
private readonly IEnumerable<IService> _services;
public MyController(IEnumerable<IService> services)
{
_services = services;
}
public void DoSomething()
{
var service = _services.Where(s => s.Name == "A").Single();
}
...
}

Extensions.cs

    public static List<Type> GetTypesAssignableFrom<T>(this Assembly assembly)
{
return assembly.GetTypesAssignableFrom(typeof(T));
}
public static List<Type> GetTypesAssignableFrom(this Assembly assembly, Type compareType)
{
List<Type> ret = new List<Type>();
foreach (var type in assembly.DefinedTypes)
{
if (compareType.IsAssignableFrom(type) && compareType != type)
{
ret.Add(type);
}
}
return ret;
}

有点晚了,但这是我的解决方案:……

Startup.cs或Program.cs if Generic Handler…

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;
}
}

希望如果这样做有任何问题,有人会好心地指出为什么这样做是错误的。

< p > Necromancing。
我认为这里的人在重新发明轮子——如果我可以这么说的话,他们做得很糟糕……
如果你想按键注册一个组件,只需使用字典:

System.Collections.Generic.Dictionary<string, IConnectionFactory> dict =
new System.Collections.Generic.Dictionary<string, IConnectionFactory>(
System.StringComparer.OrdinalIgnoreCase);


dict.Add("ReadDB", new ConnectionFactory("connectionString1"));
dict.Add("WriteDB", new ConnectionFactory("connectionString2"));
dict.Add("TestDB", new ConnectionFactory("connectionString3"));
dict.Add("Analytics", new ConnectionFactory("connectionString4"));
dict.Add("LogDB", new ConnectionFactory("connectionString5"));

然后在service-collection中注册字典:

services.AddSingleton<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(dict);

如果你不愿意获取字典并通过键访问它,你可以通过在service-collection中添加一个额外的键-查找-方法来隐藏字典 (使用委托/闭包应该给一个潜在的维护者一个机会来理解发生了什么-箭头符号有点神秘)

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美元

扩展@rnrneverdies的解决方案。代替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....
}

这里的大多数答案都违反了单一责任原则(服务类不应该自行解析依赖项)和/或使用服务定位器反模式。

避免这些问题的另一个选择是:

  • 在接口上使用附加的泛型类型参数,或者使用实现非泛型接口的新接口,
  • 实现一个适配器/拦截器类来添加标记类型,然后
  • 使用泛型类型如" name "

我已经写了一篇详细的文章:.NET中的依赖注入:解决丢失命名注册的方法

在阅读了这里的答案和其他地方的文章后,我能够让它毫无限制地工作。当你有相同接口的多个实现时,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();
}
}

自从我上面的帖子,我已经转移到通用工厂类

使用

 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);
}
}
}

我创建了自己的扩展IServiceCollection使用WithName扩展:

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");

要解析service,我们需要对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注入到应用程序的构造函数中。

我希望这能有所帮助。

我已经为此创建了一个库,实现了一些不错的功能。 代码可以在GitHub上找到:https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/ < / p >

用法很简单:

  1. 将Dazinator.Extensions.DependencyInjection包添加到你的项目中。
  2. 添加命名服务注册。
    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.


});




在上面的示例中,请注意,对于每个命名注册,您还指定了生命周期或单例、作用域或瞬态。

你可以用两种方式之一来解析服务,这取决于你是否愿意让你的服务依赖于这个包:

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一起工作,例如:

  1. 当你注册命名服务时,你注册的任何类型都可以有带参数的构造函数——它们将通过DI来满足,与AddTransient<>AddScoped<>AddSingleton<>方法的正常工作方式相同。

  2. 对于瞬态和限定作用域的命名服务,注册表会构建ObjectFactory,以便在需要时快速激活该类型的新实例。这比其他方法快得多,并且与Microsoft.Extensions.DependencyInjection的工作方式一致。

我也遇到过同样的问题,我使用了一个简单的扩展来允许Named服务。你可以在这里找到它:

它允许你添加尽可能多的(命名)服务,就像这样:

 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
}

希望能有所帮助

我知道这篇文章是几年前的了,但我总是遇到这种情况,我对服务定位器模式不满意。

此外,我知道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; }
}

现在,我知道对同一个接口有两种实现似乎有点不自然,但我确实在不止一种情况下见过这种情况。我经常遇到的问题有:

  1. 当从一个数据存储迁移到另一个数据存储时,能够使用相同的接口实现相同的逻辑操作是很有用的,这样就不需要更改调用代码。这还允许您添加在运行时在不同实现之间交换的配置(这对于回滚很有用)。
  2. 当使用装饰器模式时。您可能使用该模式的原因是,您希望在不改变接口的情况下添加功能,并在某些情况下退回到现有功能(我在向存储库类添加缓存时使用了它,因为我想在连接到缓存的周围使用类似断路器的逻辑,从而退回到基本存储库——这在缓存可用时为我提供了最佳行为,但在缓存不可用时,行为仍然有效)。

不管怎样,你可以通过在你的服务接口中添加一个类型参数来引用它们,这样你就可以实现不同的实现:

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>();

最后,在依赖于不同连接的服务的类中,你只需要对你需要的服务进行依赖,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();
}
}

这些实现甚至可以相互依赖。另一个很大的好处是可以获得编译时绑定,因此任何重构工具都可以正确工作。

希望这对将来的人有所帮助。

FooAFooBFooC实现了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));
}


....


}

如果你想将FooA更改为FooAMock用于测试:

services.AddTransient<FooAMock>();


services.AddSingleton<Func<Type, IFoo>>(x => type =>
{
if(type.Equals(typeof(FooA))
return (IFoo)x.GetService(typeof(FooAMock));
return null;
});

为什么不使用继承呢?通过这种方式,我们可以拥有尽可能多的接口副本,并且可以为每个副本选择合适的名称。我们有一个类型安全的好处

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移动到应用程序的核心,并拥有将在更高级别声明的子接口。

我认为下面文章中描述的解决方案&;Resolución dinámica de tipos en tiempo de ejecución en el competitor 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));
}

这个怎么样?您可能还可以更进一步,使用route而不是enum来解析服务类型。

接口,类型为父类,其中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) {}

我没有时间把它们通读一遍,但似乎每个人都在为根本不应该存在的问题提供解决方案。

如果你需要所有已注册的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来获得它。

好了,这里有一个使用字典的清晰易读的答案

用你的数据库Key Name创建一个enum

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);

在获得对象构造函数的依赖实例后,如下所示:

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的想法;)

任何使用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) {}
}

如果你仍然对解决多个实现感兴趣,微软推荐使用。只是在这里链接,因为我不推荐这样做。

用于多个实现的最好的文档/教程来自此源: .NET核心依赖注入-一个接口,多个实现(作者:Akshay Patel) < br > < br > 本教程中提到的示例遵循控制器/服务/存储库约定,在Startup.cs中的ConfigurationService()中使用函数实现来实例化适当/所需的接口实现。教程是我发现的澄清这个问题的最佳配方 下面,从上面提到的文章中粗略地复制/粘贴:(示例处理了购物车接口的3种不同实现,一个方法使用缓存解决方案,另一个方法使用API,其他实现使用DB。)
接口被多个实现....< / em > < br >

namespace MultipleImplementation
{
public interface IShoppingCart
{
object GetCart();
}
}


实现一个

namespace MultipleImplementation
{
public class ShoppingCartCache : IShoppingCart
{
public object GetCart()
{
return "Cart loaded from cache.";
}
}
}

< br > < br > 实现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();
}
}
}

最后,在ConfigureService方法
中,将所有内容打包到startup.cs文件中

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分钟的阅读可以让你理清思路,帮助你在一个界面中解决多个实现问题。好运!< br >

模块扩展类解决方案

很晚才回答,但我就是这么做的,这比这个问题的其他解决方法有一些优势。

优点:

  • 每个服务实现注册只需1行代码,注册方法中不需要额外的逻辑
  • 所键入的服务不需要全部在同一时间和/或地点注册。如果需要的话,甚至可以在不同的项目中进行注册,只要键是唯一的。这允许完全模块化地添加新实现。
  • 服务实例化是惰性的(+线程安全),所以当只使用一个或几个实现时,不会不必要地激活所有实现。
  • 在你的代码中不依赖任何外部委托或类型,默认情况下,服务被注入为普通的Func<TKey, TService>,但如果你愿意,很容易注册一个自定义委托或类型
  • 可以轻松选择工厂的暂态、单例或范围注册
  • 使用任何你喜欢的键类型(我强烈建议你只使用简单的类型,内置有效的相等比较,如intstringenum,或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();
});

我真的是超级晚到派对,但这是如此超级容易解决没有任何类型的工厂模式或复杂的jiggery…

面对这个问题,我想出了一个超级简单的方法。您所需要的只是一个容器来放入对象,然后注册容器。

假设你有这个(完全可重用的):

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

享受吧!

保罗

我有同样的问题,我解决使用<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;
}

下面是一个关于如何创建依赖项解析器的示例,它允许你指定一个通用参数来解析你的依赖项。


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 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>

在我的解决方案中,我使用控制器动作上的属性来代替命名服务注册。通过这种方式,我选择在动作请求的DI范围内使用服务的一个实现。

例如,如果我已经为报表端点指定了服务实现,我的代码看起来像这样:

 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中具有作用域生命周期,我使用它来选择IService的实现:

  services.AddScoped<ReportScopeContext>();
services.AddTransient<Service2>();
services.AddTransient<Service1>();
services.AddTransient<IService>(sp =>
sp.GetRequiredService<ReportScopeContext>().Active
? sp.GetRequiredService<Service1>()
: sp.GetRequiredService<Service2>());