如何在Asp.Net Core中注册同一接口的多个实现?

时间:2016-08-26 21:42:10

标签: 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)允许您通过区分它们的某些Key注册具体实现。

在Asp.Net Core中如何注册这些服务并在运行时根据某些键解决?

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

    public void ConfigureServices(IServiceCollection services)
    {            
         // How do I register services here of the same interface            
    }


    public MyController:Controller
    {
       public void DoSomeThing(string key)
       { 
          // How do get service based on key
       }
    }

工厂模式是否是唯一的选择?

UPDATE1
我已经阅读了文章here,它展示了当我们有多个concreate实现时如何使用工厂模式来获取服务实例。但它仍然不是完整的解决方案。当我调用_serviceProvider.GetService()方法时,我无法将数据注入构造函数。例如,考虑这个例子

public class ServiceA : IService
{
     private string _efConnectionString;
     ServiceA(string efconnectionString)
     {
       _efConnecttionString = efConnectionString;
     } 
}

public class ServiceB : IService
{    
   private string _mongoConnectionString;
   public ServiceB(string mongoConnectionString)
   {
      _mongoConnectionString = mongoConnectionString;
   }
}

public class ServiceC : IService
{    
    private string _someOtherConnectionString
    public ServiceC(string someOtherConnectionString)
    {
      _someOtherConnectionString = someOtherConnectionString;
    }
}

_serviceProvider.GetService()如何注入适当的连接字符串? 在Unity或任何其他IOC中,我们可以在类型注册时执行此操作。我可以使用IOption,但这需要我注入所有设置,我不能将特定的连接字符串注入服务。

另请注意,我正在尝试避免使用其他容器(包括Unity),因为我必须使用新容器注册其他所有容器(例如控制器)。

使用工厂模式创建服务实例也是针对DIP的,因为工厂增加了客户端被迫依赖details here

的依赖关系数量

所以我认为ASP.NET核心中的默认DI缺少2件事 1>使用密钥注册实例
2>在注册期间将静态数据注入构造函数

32 个答案:

答案 0 :(得分:132)

当我发现自己遇到这种情况时,我使用Func做了一个简单的解决方法。

services.AddTransient<Consumer>();

services.AddTransient<ServiceA>();
services.AddTransient<ServiceB>();
services.AddTransient<ServiceC>();

services.AddTransient<Func<string, IService>>(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 Func<string, IService> serviceAccessor;

    public Consumer(Func<string, IService> serviceAccessor)
    {
        this.serviceAccessor = serviceAccesor;
    }

    public void UseServiceA()
    {
        //use serviceAccessor field to resolve desired type
        serviceAccessor("A").DoIServiceOperation();
    }
}

<强>更新

请记住,在这个例子中,解决方案的关键是一个字符串,为了简单起见,因为OP特别要求这个案例。

但是你可以使用任何自定义分辨率类型作为键,因为你通常不需要一个巨大的n-case开关来腐烂你的代码。取决于您的应用如何扩展。

答案 1 :(得分:56)

另一种选择是使用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

答案 2 :(得分:15)

Log.ASSERT Log.DEBUG Log.ERROR Log.INFO Log.VERBOSE Log.WARN.不支持。

但您可以插入另一个依赖注入机制,例如Microsoft.Extensions.DependencyInjection See it's Home page及其GitHub Project

一点都不难:

  1. StructureMap

    中为StructureMap添加依赖项
    project.json
  2. 将其注入"Structuremap.Microsoft.DependencyInjection" : "1.0.1", 内的ASP.NET管道并注册您的课程(see docs)

    ConfigureServices
  3. 然后,要获取命名实例,您需要请求public IServiceProvider ConfigureServices(IServiceCollection services) // returns IServiceProvider ! { // Add framework services. services.AddMvc(); services.AddWhatever(); //using StructureMap; var container = new Container(); container.Configure(config => { // Register stuff in container, using the StructureMap APIs... config.For<IPet>().Add(new Cat("CatA")).Named("A"); config.For<IPet>().Add(new Cat("CatB")).Named("B"); config.For<IPet>().Use("A"); // Optionally set a default config.Populate(services); }); return container.GetInstance<IServiceProvider>(); }

    IContainer
  4. 那就是它。

    对于要构建的示例,您需要

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

答案 3 :(得分:11)

我遇到了同样的问题,想分享我是如何解决它的原因。

如你所说,有两个问题。 第一个:

  

在Asp.Net Core中,我如何注册这些服务并在以下处理它   运行时基于某些键?

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

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

  • 使用其他DI引擎(如_unityContainer.Resolve<IService>(key)

  

工厂模式是否是唯一的选择?

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

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

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

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

第二个问题:

  

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

答案 4 :(得分:10)

你是正确的,内置的ASP.NET核心容器没有注册多个服务然后检索特定服务的概念,正如你的建议,工厂是这种情况下唯一真正的解决方案。 / p>

或者,您可以切换到Unity或StructureMap等第三方容器,它确实提供了您需要的解决方案(在此处记录:https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing-the-default-services-container)。

答案 5 :(得分:6)

我只是简单地注入IEnumerable

Startup.cs中的ConfigureServices

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

答案 6 :(得分:4)

显然,您可以注入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();
    }

     .........
}

答案 7 :(得分:4)

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

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

答案 8 :(得分:3)

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

用法很简单:

  1. 将Dazinator.Extensions.DependencyInjection nuget包添加到您的项目中。
  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.

    });


在上面的示例中,请注意,对于每个命名的注册,您还指定了生存期或Singleton,Scoped或Transient。

您可以通过两种方式之一来解析服务,具体取决于您是否愿意让服务依赖于以下软件包:

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<>和{{1 }}方法正常工作。

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

答案 9 :(得分:3)

此处的大多数答案都违反了单一责任原则(服务类不应自行解决依赖关系)和/或使用服务定位器反模式。

避免这些问题的另一种方法是:

  • 在接口上使用附加的通用类型参数,或者在实现非通用接口的新接口上使用
  • 实现适配器/拦截器类以添加标记类型,然后添加
  • 使用通用类型作为“名称”

我写了一篇更详细的文章:Dependency Injection in .NET: A way to work around missing named registrations

答案 10 :(得分:2)

我认为以下文章“Resolución dinámica de tipos en tiempo de ejecución en el contenedor de IoC de .NET Core”中描述的解决方案更简单,不需要工厂。

您可以使用通用接口

public interface IService<T> where T : class {}

然后在 IoC 容器上注册所需的类型:

services.AddTransient<IService<ServiceA>, ServiceA>();
services.AddTransient<IService<ServiceB>, ServiceB>();

之后,您必须按如下方式声明依赖项:

private readonly IService<ServiceA> _serviceA;
private readonly IService<ServiceB> _serviceB;

public WindowManager(IService<ServiceA> serviceA, IService<ServiceB> serviceB)
{
    this._serviceA = serviceA ?? throw new ArgumentNullException(nameof(serviceA));
    this._serviceB = serviceB ?? throw new ArgumentNullException(nameof(ServiceB));
}

答案 11 :(得分: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>();

最后,在依赖于具有不同连接的Service的类中,您只需依赖所需的服务,DI框架将处理其余的工作:

[Route("api/v1)]
[ApiController]
public class ControllerWhichNeedsMongoService {  
  private readonly IService<MongoDataSource> _mongoService;
  private readonly IService<SqlDataSource> _sqlService ;

  public class ControllerWhichNeedsMongoService(
    IService<MongoDataSource> mongoService, 
    IService<SqlDataSource> sqlService
  )
  {
    _mongoService = mongoService;
    _sqlService = sqlService;
  }

  [HttpGet]
  [Route("demo")]
  public async Task GetStuff()
  {
    if(useMongo)
    {
       await _mongoService.DoServiceOperation();
    }
    await _sqlService.DoServiceOperation();
  }
}

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

希望这对以后的人有帮助。

答案 12 :(得分:2)

我发现的最佳文档/教程用于多种实现来自以下来源: .NET Core Dependency Injection - One Interface, Multiple Implementations, (Authored by Akshay Patel)

教程中提到的示例遵循 Controller/Service/Repository 对流,在 Startup.cs 的 ConfigurationService() 中使用 Func 实现来实例化正确/需要的接口实现;教程是我找到的最好的方法来阐明这个问题。

下面是上述文章的粗鲁复制/粘贴:(示例涉及购物车界面的 3 种不同实现,一种使用缓存解决方案,另一种使用 API,其他使用 DB 实现。)
需要多次实现的接口....

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


实现 A

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


实施 B

namespace MultipleImplementation  
{  
    public class ShoppingCartDB : IShoppingCart  
    {  
        public object GetCart()  
        {  
            return "Cart loaded from DB";  
        }  
    }  
}  


实现 C

namespace MultipleImplementation  
{  
    public class ShoppingCartAPI : IShoppingCart  
    {  
        public object GetCart()  
        {  
            return "Cart loaded through API.";  
        }  
    }  
}  


将使用存储库中的接口声明来选择 A、B、C....

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


枚举来选择将使用哪个实现...

namespace MultipleImplementation  
{  
    public class Constants  
    {  
    }  
  
    public enum CartSource  
    {  
        Cache=1,  
        DB=2,  
        API=3  
    }  
}  


声明的存储库接口的实现(谁将选择哪个实现...

using System;  
  
namespace MultipleImplementation  
{  
    public class ShoppingCartRepository : IShoppingCartRepository  
    {  
        private readonly Func<string, IShoppingCart> shoppingCart;  
        public ShoppingCartRepository(Func<string, IShoppingCart> shoppingCart)  
        {  
            this.shoppingCart = shoppingCart;  
        }  
  
        public object GetCart()  
        {  
            return shoppingCart(CartSource.DB.ToString()).GetCart();  
        }  
    }  
}  


最后,在 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 分钟将使头脑清醒,帮助您将多个实现解决到一个界面中。祝你好运!

答案 13 :(得分:2)

迟到了这个聚会,但这是我的解决方案:...

Startup.cs或Program.cs(如果是通用处理程序...

services.AddTransient<IMyInterface<CustomerSavedConsumer>, CustomerSavedConsumer>();
services.AddTransient<IMyInterface<ManagerSavedConsumer>, ManagerSavedConsumer>();

T接口设置的IMyInterface

public interface IMyInterface<T> where T : class, IMyInterface<T>
{
    Task Consume();
}

T的IMyInterface的具体实现

public class CustomerSavedConsumer: IMyInterface<CustomerSavedConsumer>
{
    public async Task Consume();
}

public class ManagerSavedConsumer: IMyInterface<ManagerSavedConsumer>
{
    public async Task Consume();
}

希望这样做有任何问题,有人会指出为什么这是错误的方法。

答案 14 :(得分:2)

虽然似乎@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所说的。虽然我不同意每一点,但这篇文章很不错,因为我认为你确实需要命名实例。一旦我再次发现,我会在这里发布该链接。

事实上,您不需要传递选择器或访问器:

我在我的项目中使用以下代码,到目前为止效果很好。

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

答案 15 :(得分:1)

创新。
我认为这里的人们正在重新发明轮子-糟糕的是,如果我能这么说的话...
如果要通过键注册组件,只需使用字典:

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中添加其他key-lookup-method来隐藏字典:
(使用委托/关闭应该使潜在的维护者有机会了解正在发生的事情-箭头表示有点神秘)

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

答案 16 :(得分:1)

模块化扩展类解决方案

很晚才回答,但这是我这样做的方式,与此问题的其他一些解决方案相比,它具有一些优势。

优点:

  • 每个服务实现注册只需 1 行代码,注册方法中不需要额外的逻辑
  • 密钥服务不需要在同一时间和/或地点全部注册。如果需要,甚至可以在不同的项目中进行注册,只要密钥是唯一的。这允许以完全模块化的方式添加新的实现。
  • 服务实例化是惰性的(+ 线程安全),因此在只使用一个或几个实现时不会不必要地激活所有实现。
  • 在您的代码中不依赖任何外部委托或类型,默认情况下该服务作为普通的 Func<TKey, TService> 注入,​​但如果您愿意,也可以轻松注册自定义委托或类型
  • 易于在工厂的瞬态、单例或范围注册之间进行选择
  • 使用您喜欢的任何键类型(我强烈建议您只使用带有内置有效相等比较的简单类型,例如 intstringenum 或 {{1 }} 因为为什么要让生活变得比它需要的更复杂)

配置示例:

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

答案 17 :(得分:1)

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

启动.配置服务

services.AddScoped<ITodoService, TodoService>();
services.AddScoped<IValuesService, ValuesService>();

用法

public class UsageClass {
 public UsageClass(ITodoService todoService, IValuesService valuesService) {}
}

如果您仍然对解决多个实现感兴趣,THIS 是 Microsoft 的建议。只是在这里链接,因为这不是我推荐的。

答案 18 :(得分:1)

我使用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");

要解决服务,我们需要像这样在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

我希望这会有所帮助。

答案 19 :(得分:1)

我的解决方案值得考虑...考虑切换到温莎城堡,因为不能说我喜欢上面的任何解决方案。抱歉!

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

答案 20 :(得分:1)

工厂方法当然可行。另一种方法是使用继承来创建从IService继承的单个接口,在IService实现中实现继承的接口,并注册继承的接口而不是基础接口。是否添加继承层次结构或工厂是“正确”模式都取决于您与谁交谈。在处理使用通用(例如IRepository<T>)作为数据访问基础的同一应用程序中的多个数据库提供程序时,我经常必须使用此模式。

示例接口和实现:

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

答案 21 :(得分:1)

为什么不使用继承?这样,我们可以拥有所需数量的接口副本,并且可以为每个接口选择合适的名称。而且我们受益于类型安全

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

答案 22 :(得分:0)

好的,这是使用字典的清晰可读的答案

使用您的数据库密钥名称创建一个枚举

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

答案 23 :(得分:0)

这个怎么样?您可以更进一步,使用路由而不是枚举来解析服务类型。

输入到父类的接口,其中 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) {}

答案 24 :(得分:0)

我真的超级迟到了,但是这个问题超级容易解决,没有任何工厂模式或复杂的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");
})

我创建了一个“GetRequiredRegisteredService”版本而不是“GetRequiredService”扩展方法,以便更轻松地解决相互依赖的问题:

.AddSingleton(serviceProvider =>
{
    var myURL = serviceProvider.GetRequiredRegisteredService<IRestRequest, string>();
    return new new RestRequest(myURL);
})

一旦您看到修复程序是多么容易,那么它就会非常容易使用。

哦,甚至超级容易安装:

Install-Package CodeRed.Extensions.DependencyInjection.DependencyRegister

享受吧!

保罗

答案 25 :(得分:0)

我遇到了同样的问题,我使用一个简单的扩展程序来允许命名服务。您可以在这里找到它:

它允许您像这样添加任意数量的(命名)服务:

 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
    }

希望有帮助

答案 26 :(得分:0)

自从以上内容以来,我已经转到通用工厂类

用法

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

答案 27 :(得分:0)

在阅读了这里的答案以及其他地方的文章后,我可以不用字符串就可以使用它。当您具有相同接口的多个实现时,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();
    }
}

答案 28 :(得分:0)

扩展@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....
}

答案 29 :(得分:0)

关于服务的服务怎么样?

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

赞:

public interface INamedService
{
    string Name { get; }
}

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

因此,您的IMyService必须实现INamedService,但是您将获得所需的基于密钥的解决方案,对吧?

为了公平起见,甚至必须拥有此INamedService接口似乎很丑陋,但是如果您想走得更远并使事情变得更优雅,则可以在实现/类上找到[NamedServiceAttribute(“ A”)]扩展中的代码,效果也一样。更公平地说,反射速度很慢,因此可能需要进行优化,但是老实说,这是DI引擎应该一直在帮助的事情。速度和简单性是TCO的重要贡献。

总而言之,不需要显式工厂,因为“查找命名服务”是可重用的概念,并且工厂类不能扩展为解决方案。 Func <>看起来不错,但是 bleh 这样的切换块是如此,再一次,您编写Funcs的次数将与编写工厂的次数相同。以更少的代码开始简单,可重用的代码,如果事实证明并非如此,那么就变得复杂了。

答案 30 :(得分:0)

虽然开箱即用的实现并未提供,但这里有一个示例项目,允许您注册命名实例,然后将INamedServiceFactory注入代码并按名称提取实例。与此处的其他辅助解决方案不同,它允许您注册相同实现的多个实例,但配置不同

https://github.com/macsux/DotNetDINamedInstances

答案 31 :(得分:-1)

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