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

LP1*_*P13 183 c# asp.net-core-mvc coreclr asp.net-core

我有来自相同接口的服务

public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { } 
public class ServiceC : IService { }
Run Code Online (Sandbox Code Playgroud)

通常,其他IOC容器Unity允许您通过某些Key区分它们来注册具体实现.

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

我没有看到任何通常用于区分具体实现的AddService方法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
       }
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

public class ServiceC : IService
{    
    private string _someOtherConnectionString
    public ServiceC(string someOtherConnectionString)
    {
      _someOtherConnectionString = someOtherConnectionString;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

同时使用工厂模式来创建服务实例是针对DIP的,因为工厂增加了客户端被迫依赖于此处的详细信息的依赖项数量

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

Mig*_*lla 186

Func当我发现自己处于这种情况时,我做了一个简单的解决方法.

public delegate IService ServiceResolver(string key);
Run Code Online (Sandbox Code Playgroud)

并在DI注册的任何类中使用它,如:

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

services.AddTransient<ServiceResolver>(serviceProvider => key =>
{
    switch (key)
    {
        case "A":
            return serviceProvider.GetService<ServiceA>();
        case "B":
            return serviceProvider.GetService<ServiceB>();
        case "C":
            return serviceProvider.GetService<ServiceC>();
        default:
            throw new KeyNotFoundException(); // or maybe return null, up to you
    }
});
Run Code Online (Sandbox Code Playgroud)

UPDATE

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

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

  • 使用这样的工厂模式是最好的方法。感谢分享! (3认同)
  • 不赞成投票的解释-非常有趣,但我目前正在重构庞大的代码库,以删除几年前(在MS DI革命之前)某人所做的所有Func魔术操作。问题在于,它极大地增加了属性的连续性复杂性,可能会导致更复杂的DI分辨率下降。例如,我在一个Windows服务处理程序上工作过,其中有1.6k行以上的代码与Func有关,在完成推荐的DI方法之后,我将其减少至0.2k行。OK行代码没有任何意义..除了它现在更易于阅读和重用... (3认同)
  • @MatthewStevenMonkan 用一个例子更新了我的答案 (2认同)
  • +1非常整洁干净,因为当我们使用其他di-container时,我们必须在需要解析依赖项时包含它们的包,例如.AutoFac中的ILifetimeScope. (2认同)
  • @AnupamSingh 在我看来,大多数在 .NET Core 上运行的中小型应用程序不需要任何 DI 框架,只是增加了复杂性和不需要的依赖项,内置 DI 的美观和简单性已经足够了,它可以也可以轻松扩展。 (2认同)
  • 我明白这是一个解决方法。这不完全是一个答案,所以我不能投票。我不会投反对票,因为有明确的通知表明这是一种解决方法。如果我们指望依赖注入来将接口的客户端与实现的知识隔离开来,那么这是行不通的。客户c'tor必须做出选择。 (2认同)
  • 否决票解释 - 我同意 @PiotrKula 的观点,并在下面的回答中有完整的实现。我们可以通过一种非常简单的方式消除密钥的概念。检查下面我的答案。 (2认同)

rnr*_*ies 65

另一种选择是使用扩展方法GetServicesMicrosoft.Extensions.DependencyInjection.

将您的服务注册为:

services.AddSingleton<IService, ServiceA>();
services.AddSingleton<IService, ServiceB>();
services.AddSingleton<IService, ServiceC>();
Run Code Online (Sandbox Code Playgroud)

然后用一点Linq解决:

var services = serviceProvider.GetServices<IService>();
var serviceB = services.First(o => o.GetType() == typeof(ServiceB));
Run Code Online (Sandbox Code Playgroud)

要么

var serviceZ = services.First(o => o.Name.Equals("Z"));
Run Code Online (Sandbox Code Playgroud)

(假设IService有一个名为"Name"的字符串属性)

一定要有 using Microsoft.Extensions.DependencyInjection;

更新

AspNet 2.1来源: GetServices

  • serviceProvider.GetServices <IService>()将实例化ServiceA,ServiceB和ServiceC中的每一个.您只想调用一个服务的构造函数 - 您实际需要的服务.如果实现不是轻量级或者您有很多IService实现(例如,您为每个模型自动生成IRepository实现),这是一个大问题. (14认同)
  • 不确定,但我认为这不是确定性的.你今天得到的任何结果明天都可能改变,这似乎不是一个好习惯. (6认同)
  • 我同意@Uros。这不是一个好的解决方案。想象一下,如果您注册10个IService实现,而实际需要的实例是最后一个实例,将会发生什么。在这种情况下,DI实际创建了9个实例,但从未使用过。 (6认同)
  • 你能在Controller中展示一下使用的例子吗? (4认同)
  • upvote到GetServices的链接,它告诉我你可以通过请求`IEnumerable <IService>来请求服务列表的依赖服务 (3认同)
  • 如果我理解正确,您将把 ServiceProvide 注入到对象中,然后使用它来要求正确的实现。通过这种方式,您不再进行依赖注入,而是遵循被视为反模式的服务定位器模式。 (2认同)
  • 坏主意:多个未使用的实例,服务定位器反模式以及直接耦合到实际实现(typeof &lt;ServiceA&gt;)。 (2认同)
  • 我本身没有看到反模式,如果您对同一问题有不同的实现(稍后可能会添加更多)并且运行时的某些因素决定使用哪一个,您可以获得服务的集合而不是过滤器对于匹配的 - 我们在 JAVA 中使用 Spring 和 OSGi 中的类似模式 (2认同)
  • 是好是坏?依靠。我有一个用例,其中要使用接口的多个实现。 (2认同)
  • 我简直不敢相信,最热门的答案竟然得到了这么多票。这两者都依赖于ServiceLocator。虽然我理解重构大量代码库的吸引力以及这个答案和另一个答案带来的简单性,但我见过的最好的答案是这个:/sf/answers/3673629011/。这个答案更好,因为它适合更坚实的思维方式,并且没有引入另一个框架。 (2认同)

小智 39

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

示例接口和实现:

public interface IService 
{
}

public interface IServiceA: IService
{}

public interface IServiceB: IService
{}

public interface IServiceC: IService
{}

public class ServiceA: IServiceA 
{}

public class ServiceB: IServiceB
{}

public class ServiceC: IServiceC
{}
Run Code Online (Sandbox Code Playgroud)

容器:

container.Register<IServiceA, ServiceA>();
container.Register<IServiceB, ServiceB>();
container.Register<IServiceC, ServiceC>();
Run Code Online (Sandbox Code Playgroud)

  • 这个答案必须是第一个 (5认同)
  • 当我需要注入最通用的类​​型(IService)时,这对注入没有帮助 - 但针对不同的用例有不同的实现 (3认同)

Ric*_*ter 21

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

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

  • 在接口上使用额外的泛型类型参数或实现非泛型接口的新接口,
  • 实现一个适配器/拦截器类来添加标记类型,然后
  • 使用泛型类型作为“名称”

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

  • 请参阅 /sf/answers/3644622761/ 的评论,并且在接受的答案中,服务被延迟解析,即您只知道它是否在运行时失败,并且无法在容器启动后静态检查它构建(类似于评论中的答案)。SRP,因为服务不仅负责其业务逻辑,还负责依赖关系解析 (3认同)

Gra*_*ray 18

这个派对有点晚了,但这是我的解决方案:...

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

services.AddTransient<IMyInterface<CustomerSavedConsumer>, CustomerSavedConsumer>();
services.AddTransient<IMyInterface<ManagerSavedConsumer>, ManagerSavedConsumer>();
Run Code Online (Sandbox Code Playgroud)

T 接口设置的 IMyInterface

public interface IMyInterface<T> where T : class, IMyInterface<T>
{
    Task Consume();
}
Run Code Online (Sandbox Code Playgroud)

T的IMyInterface的具体实现

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

public class ManagerSavedConsumer: IMyInterface<ManagerSavedConsumer>
{
    public async Task Consume();
}
Run Code Online (Sandbox Code Playgroud)

访问控制器中的服务

public class MyController
{
    private readonly IMyInterface<CustomerSavedConsumer> _customerSavedConsumer;
    private readonly IMyInterface<ManagerSavedConsumer> _managerSavedConsumer;

    public MyController(IMyInterface<CustomerSavedConsumer> customerSavedConsumer, IMyInterface<ManagerSavedConsumer> managerSavedConsumer)
    {
        _customerSavedConsumer = customerSavedConsumer;
        _managerSavedConsumer = managerSavedConsumer;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • @Gray 尽管您的帖子受到了一些负面报道,但我感谢您提出这个解决方案。它为读者提供了另一种选择来克服 .net cores DI 的限制。虽然它可能无法直接回答OP的问题,但它提供了一个完美的替代解决方案,这就是SO的全部意义,对吗? (9认同)
  • `IMyInterface&lt;CustomerSavedConsumer&gt;` 和 `IMyInterface&lt;ManagerSavedConsumer&gt;` 是 *不同的* 服务类型 - 这根本不回答 OP 问题。 (6认同)
  • OP 想要一种在 Asp.net 核心中注册同一接口的多个实现的方法。如果我没有这样做,请解释如何(确切地)。 (5认同)
  • 这是最好的...完整的 C# 和 OOP 以及 KISS。将字符串键留给 Javascripters,我们拥有功能齐全的类型系统。如果我们需要更加灵活,总是可以进行反思。 (5认同)
  • @Gray 这解决了如何处理接口的多个具体实现并执行 DI 的问题。感谢您的解决方案。 (3认同)
  • 虽然您是对的,但这种模式可以实现操作员想要的效果。至少当我自己尝试这样做时,我偶然发现了这篇文章,我的解决方案最适合我的情况。 (2认同)
  • 我预计问题更多的是为单个接口注册多个实现(使用 MS DI)不允许容器将一个实现与另一个区分开来。在其他 DI 中,您可以对它们进行键控,以便容器知道选择哪个。在 MS 中,您_必须_使用委托并手动选择。您的解决方案没有解决这种情况,因为您的接口不同,因此容器选择正确的实现没有问题。虽然您的示例显然有效,但它不是所述问题的解决方案。 (2认同)
  • @Gray SO 是关于分享解决问题中提出的问题的解决方案,你做到了。你的解决方案是我一直解决这个问题的方法。这回答了这个问题试图以更干净、更简单的方式解决的问题的核心。这样,解决同一问题就有多种选择,这就是软件工程的意义所在。 (2认同)
  • @Gray 感谢您发布这个答案。我想知道这个问题有一段时间了,这个答案可能是迄今为止我在这里看到的最好的答案,尽管有些人抱怨这个解决方案,但这肯定是迄今为止最好的答案。 (2认同)

Ger*_*oli 16

它不受支持Microsoft.Extensions.DependencyInjection.

但是你可以插入另一个依赖注入机制,比如StructureMap See it's Home page和它的GitHub Project.

这根本不难:

  1. 在您的project.json:中向StructureMap添加依赖项:

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
    
    Run Code Online (Sandbox Code Playgroud)
  2. 将它注入ASP.NET管道ConfigureServices并注册您的类(参见文档)

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

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

而已.

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

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

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

        public string Name {get; set; }
    }
Run Code Online (Sandbox Code Playgroud)

  • 将容器注入到控制器中违背了 IoC 的全部目的。 (6认同)

Ste*_*ger 12

死灵法术。
我认为这里的人们正在重新发明轮子 - 很糟糕,如果我可以这么说......
如果你想通过键注册一个组件,只需使用字典:

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

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

然后在服务集合中注册字典:

services.AddSingleton<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(dict);
Run Code Online (Sandbox Code Playgroud)

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

services.AddTransient<Func<string, IConnectionFactory>>(
    delegate (IServiceProvider sp)
    {
        return
            delegate (string key)
            {
                System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService
 <System.Collections.Generic.Dictionary<string, IConnectionFactory>>(sp);

                if (dbs.ContainsKey(key))
                    return dbs[key];

                throw new System.Collections.Generic.KeyNotFoundException(key); // or maybe return null, up to you
            };
    });
Run Code Online (Sandbox Code Playgroud)

现在您可以使用以下任一方式访问您的类型

IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<Func<string, IConnectionFactory>>(serviceProvider)("LogDB");
logDB.Connection
Run Code Online (Sandbox Code Playgroud)

或者

System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider);
dbs["logDB"].Connection
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,第一个完全是多余的,因为你也可以用字典来做到这一点,而不需要闭包和 AddTransient(如果你使用 VB,甚至大括号也不会有不同):

IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider)["logDB"];
logDB.Connection
Run Code Online (Sandbox Code Playgroud)

(越简单越好 - 不过您可能想将其用作扩展方法)

当然,如果你不喜欢字典,你也可以为你的界面配备一个属性Name(或其他),然后按键查找:

services.AddSingleton<IConnectionFactory>(new ConnectionFactory("ReadDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("WriteDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("TestDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("Analytics"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("LogDB"));



// /sf/ask/2742249261/
services.AddTransient<Func<string, IConnectionFactory>>(
    delegate(IServiceProvider sp)
    {
        return
            delegate(string key)
            {
                System.Collections.Generic.IEnumerable<IConnectionFactory> svs = 
                    sp.GetServices<IConnectionFactory>();
                
                foreach (IConnectionFactory thisService in svs)
                {
                    if (key.Equals(thisService.Name, StringComparison.OrdinalIgnoreCase))
                        return thisService;
                }
    
                return null;
            };
    });
Run Code Online (Sandbox Code Playgroud)

但这需要更改您的界面以适应该属性,并且循环遍历大量元素应该比关联数组查找(字典)慢得多。
不过,很高兴知道它可以在没有字典的情况下完成。

这些只是我的 0.05 美元


Zac*_*ins 12

以下是有关如何创建依赖关系解析器的示例,该依赖关系解析器允许您指定通用参数来解析依赖关系。


var serviceProvider = new ServiceCollection()
    .AddSingleton<IPerson, Larry>()
    .AddSingleton<IPerson, Phil>()
    .AddSingleton<IDependencyResolver<IPerson, string>, PersonDependecyResolver>()
    .BuildServiceProvider();

var persons = serviceProvider.GetService<IDependencyResolver<IPerson, string>>();
Console.WriteLine(persons.GetDependency("Phil").GetName());
Run Code Online (Sandbox Code Playgroud)
public interface IDependencyResolver<out TResolve, in TArg>
{
    TResolve GetDependency(TArg arg);
}
Run Code Online (Sandbox Code Playgroud)
public class PersonDependecyResolver : IDependencyResolver<IPerson, string>
{
    private readonly IEnumerable<IPerson> people;

    public PersonDependecyResolver(IEnumerable<IPerson> people)
    {
        this.people = people;
    }
        
    public IPerson GetDependency(string arg)
    {
        return arg switch
        {
            "Larry" => this.people.FirstOrDefault(p => p.GetType() == typeof(Larry)),
            "Phil" => this.people.FirstOrDefault(p => p.GetType() == typeof(Phil)),
            _ => throw new Exception("Unable to resolve dependency")
        }
     
        ?? throw new Exception($"No type was found for argument {arg}");
    }
}
Run Code Online (Sandbox Code Playgroud)


Mus*_*tor 12

>= .NET 8:键控 DI 服务

通过 .NET 8,我们获得了扩展库的更新,它带来了键控 DI 服务,而这正是您所需要的。

这里引用了 Microsoft 文档:What's new in .NET 8? - 密钥 DI 服务


密钥 DI 服务

键控依赖注入 (DI) 服务提供了一种使用密钥注册和检索 DI 服务的方法。通过使用密钥,您可以确定注册和使用服务的方式。以下是一些新的 API:

以下示例向您展示如何使用密钥 DI 服务。

using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<BigCacheConsumer>();
builder.Services.Addsingleton<SmallCacheConsumer>();

builder.Services.AddKeyedSingleton<IMemoryCache, BigCache>("big");
builder.Services.AddKeyedSingleton<IMemoryCache, SmallCache>("small");

var app = builder.Build();

app.MapGet("/big", (BigCacheConsumer data) => data.GetData());
app.MapGet("/small", (SmallCacheConsumer data) => data.GetData());

app.Run();

class BigCacheConsumer([FromKeyedServices("big")] IMemoryCache cache)
{
    public object? GetData() => cache.Get("data");
}

class SmallCacheConsumer(IKeyedServiceProvider keyedServiceProvider)
{
    public object? GetData() => keyedServiceProvider.GetRequiredKeyedService<IMemoryCache>("small");
}
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请参阅dotnet/runtime#64427



nel*_*eus 11

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

如你所说,有两个问题.首先:

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

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

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

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

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

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

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

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

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

第二个问题:

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

首先,我同意你的意见,取决于新的东西IOptions(因此在包装上Microsoft.Extensions.Options.ConfigurationExtensions)并不是一个好主意.我见过一些讨论IOptions关于它的好处的不同意见的地方.同样,我尝试避免在不需要时添加新的依赖项.真的需要吗?我想不是.否则每个实现都必须依赖它而不需要来自该实现(对我而言,它看起来像违反了ISP,我同意你的意见).这也是有关取决于工厂,但在这种情况下,真正可以被避免.

ASP.NET Core DI为此提供了非常好的重载:

var mongoConnection = //...
var efConnection = //...
var otherConnection = //...
services.AddTransient<IMyFactory>(
             s => new MyFactoryImpl(
                 mongoConnection, efConnection, otherConnection, 
                 s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));
Run Code Online (Sandbox Code Playgroud)

  • @cagatay-kalan 如果 OP 提出问题,他可以使用 ASP.NET Core DI 轻松实现他的目标。无需其他 DI 框架。 (2认同)

Soc*_*ock 10

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

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


Jac*_*rts 10

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

如果您需要所有已注册的 IService 实现,那么您就需要它们全部。但不要将它们全部注入 IEnumerable,然后使用逻辑根据某种类型的键选择一个。这样做的问题是你需要一个密钥,并且如果密钥发生变化,逻辑不需要改变,即;IService 的不同实现,因此 typeof 不再起作用。

真正的问题

这里的业务逻辑应该位于引擎服务中。需要像 IServiceDecisionEngine 这样的东西。IServiceDecisionEngine 的实现仅从 DI 获取所需的 IService 实现。喜欢

public class ServiceDecisionEngine<SomeData>: IServiceDecisionEngine<T> 
{
    public ServiceDecisionEngine(IService serviceA, IService serviceB) { }

    public IService ResolveService(SomeData dataNeededForLogic)
    {
        if (dataNeededForLogic.someValue == true) 
        { 
            return serviceA;
        } 
        return serviceB;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在在您的 DI 中,您可以这样做,.AddScoped<IServiceDecisionEngine<SomeData>, new ServiceDecisionEngine(new ServiceA(), new ServiceB())需要 IService 的 managerService 将通过注入和使用 IServiceDecisionEngine 来获取它。


Art*_*nig 10

我发现的关于多个实现的最佳文档/教程来自以下来源: .NET Core 依赖注入 - 一个接口,多个实现,(由 Akshay Patel 撰写)

教程中提到的示例遵循控制器/服务/存储库约定,并具有Func实现在 Startup.cs 的 ConfigurationService() 中实例化正确/需要的接口实现。教程是我发现澄清这个问题的最佳方法。

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

namespace MultipleImplementation  
{  
    public interface IShoppingCart  
    {  
        object GetCart();  
    }  
}  
Run Code Online (Sandbox Code Playgroud)


实施方案A

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


实施B

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


实施C

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


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

namespace MultipleImplementation  
{  
    public interface IShoppingCartRepository  
    {  
        object GetCart();  
    }  
}
Run Code Online (Sandbox Code Playgroud)


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

namespace MultipleImplementation  
{  
    public class Constants  
    {  
    }  
  
    public enum CartSource  
    {  
        Cache=1,  
        DB=2,  
        API=3  
    }  
}  
Run Code Online (Sandbox Code Playgroud)


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

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


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

public void ConfigureServices(IServiceCollection services)  
        {  
  
            services.AddScoped<IShoppingCartRepository, ShoppingCartRepository>();  
  
            services.AddSingleton<ShoppingCartCache>();  
            services.AddSingleton<ShoppingCartDB>();  
            services.AddSingleton<ShoppingCartAPI>();  
  
            services.AddTransient<Func<string, IShoppingCart>>(serviceProvider => key =>  
            {  
                switch (key)  
                {  
                    case "API":  
                        return serviceProvider.GetService<ShoppingCartAPI>();  
                    case "DB":  
                        return serviceProvider.GetService<ShoppingCartDB>();  
                    default:  
                        return serviceProvider.GetService<ShoppingCartCache>();  
                }  
            });  
  
            services.AddMvc();  
        }  
Run Code Online (Sandbox Code Playgroud)

在此,我强调,6 分钟的阅读时间将让你理清思绪,帮助你解决在一个接口中实现多个实现的问题。祝你好运!


T B*_*own 9

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

用法

 services.AddFactory<IProcessor, string>()
         .Add<ProcessorA>("A")
         .Add<ProcessorB>("B");

 public MyClass(IFactory<IProcessor, string> processorFactory)
 {
       var x = "A"; //some runtime variable to select which object to create
       var processor = processorFactory.Create(x);
 }
Run Code Online (Sandbox Code Playgroud)

执行

public class FactoryBuilder<I, P> where I : class
{
    private readonly IServiceCollection _services;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public FactoryBuilder(IServiceCollection services)
    {
        _services = services;
        _factoryTypes = new FactoryTypes<I, P>();
    }
    public FactoryBuilder<I, P> Add<T>(P p)
        where T : class, I
    {
        _factoryTypes.ServiceList.Add(p, typeof(T));

        _services.AddSingleton(_factoryTypes);
        _services.AddTransient<T>();
        return this;
    }
}
public class FactoryTypes<I, P> where I : class
{
    public Dictionary<P, Type> ServiceList { get; set; } = new Dictionary<P, Type>();
}

public interface IFactory<I, P>
{
    I Create(P p);
}

public class Factory<I, P> : IFactory<I, P> where I : class
{
    private readonly IServiceProvider _serviceProvider;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public Factory(IServiceProvider serviceProvider, FactoryTypes<I, P> factoryTypes)
    {
        _serviceProvider = serviceProvider;
        _factoryTypes = factoryTypes;
    }

    public I Create(P p)
    {
        return (I)_serviceProvider.GetService(_factoryTypes.ServiceList[p]);
    }
}
Run Code Online (Sandbox Code Playgroud)

延期

namespace Microsoft.Extensions.DependencyInjection
{
    public static class DependencyExtensions
    {
        public static FactoryBuilder<I, P> AddFactory<I, P>(this IServiceCollection services)
            where I : class
        {
            services.AddTransient<IFactory<I, P>, Factory<I, P>>();
            return new FactoryBuilder<I, P>(services);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • `_services.AddSingleton(_factoryTypes);` 我觉得这一行应该在FactoryBuilder构造函数中,否则每次调用add时都会调用它。 (2认同)

Arc*_*ade 7

显然,您只需注入服务接口的 IEnumerable 即可!然后使用 LINQ 找到您想要的实例。

我的示例是针对 AWS SNS 服务的,但您实际上可以对任何注入的服务执行相同的操作。

启动

foreach (string snsRegion in Configuration["SNSRegions"].Split(',', StringSplitOptions.RemoveEmptyEntries))
{
    services.AddAWSService<IAmazonSimpleNotificationService>(
        string.IsNullOrEmpty(snsRegion) ? null :
        new AWSOptions()
        {
            Region = RegionEndpoint.GetBySystemName(snsRegion)
        }
    );
}

services.AddSingleton<ISNSFactory, SNSFactory>();

services.Configure<SNSConfig>(Configuration);
Run Code Online (Sandbox Code Playgroud)

配置文件

public class SNSConfig
{
    public string SNSDefaultRegion { get; set; }
    public string SNSSMSRegion { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

appsettings.json

  "SNSRegions": "ap-south-1,us-west-2",
  "SNSDefaultRegion": "ap-south-1",
  "SNSSMSRegion": "us-west-2",
Run Code Online (Sandbox Code Playgroud)

SNS工厂

public class SNSFactory : ISNSFactory
{
    private readonly SNSConfig _snsConfig;
    private readonly IEnumerable<IAmazonSimpleNotificationService> _snsServices;

    public SNSFactory(
        IOptions<SNSConfig> snsConfig,
        IEnumerable<IAmazonSimpleNotificationService> snsServices
        )
    {
        _snsConfig = snsConfig.Value;
        _snsServices = snsServices;
    }

    public IAmazonSimpleNotificationService ForDefault()
    {
        return GetSNS(_snsConfig.SNSDefaultRegion);
    }

    public IAmazonSimpleNotificationService ForSMS()
    {
        return GetSNS(_snsConfig.SNSSMSRegion);
    }

    private IAmazonSimpleNotificationService GetSNS(string region)
    {
        return GetSNS(RegionEndpoint.GetBySystemName(region));
    }

    private IAmazonSimpleNotificationService GetSNS(RegionEndpoint region)
    {
        IAmazonSimpleNotificationService service = _snsServices.FirstOrDefault(sns => sns.Config.RegionEndpoint == region);

        if (service == null)
        {
            throw new Exception($"No SNS service registered for region: {region}");
        }

        return service;
    }
}

public interface ISNSFactory
{
    IAmazonSimpleNotificationService ForDefault();

    IAmazonSimpleNotificationService ForSMS();
}
Run Code Online (Sandbox Code Playgroud)

现在您可以在您的自定义服务或控制器中获取您想要的区域的 SNS 服务

public class SmsSender : ISmsSender
{
    private readonly IAmazonSimpleNotificationService _sns;

    public SmsSender(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForSMS();
    }

    .......
 }

public class DeviceController : Controller
{
    private readonly IAmazonSimpleNotificationService _sns;

    public DeviceController(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForDefault();
    }

     .........
}
Run Code Online (Sandbox Code Playgroud)


Ole*_*run 7

为什么不使用继承?通过这种方式,我们可以拥有任意数量的界面副本,并且可以为每个副本选择合适的名称。我们有类型安全的好处

public interface IReportGenerator
public interface IExcelReportGenerator : IReportGenerator
public interface IPdfReportGenerator : IReportGenerator
Run Code Online (Sandbox Code Playgroud)

具体类:

public class ExcelReportGenerator : IExcelReportGenerator
public class PdfReportGenerator : IPdfReportGenerator
Run Code Online (Sandbox Code Playgroud)

登记:

代替

services.AddScoped<IReportGenerator, PdfReportGenerator>();
services.AddScoped<IReportGenerator, ExcelReportGenerator>();
Run Code Online (Sandbox Code Playgroud)

我们有 :

services.AddScoped<IPdfReportGenerator, PdfReportGenerator>();
services.AddScoped<IExcelReportGenerator, ExcelReportGenerator>();
Run Code Online (Sandbox Code Playgroud)

客户:

public class ReportManager : IReportManager
{
    private readonly IExcelReportGenerator excelReportGenerator;
    private readonly IPdfReportGenerator pdfReportGenerator;

    public ReportManager(IExcelReportGenerator excelReportGenerator, 
                         IPdfReportGenerator pdfReportGenerator)
    {
        this.excelReportGenerator = excelReportGenerator;
        this.pdfReportGenerator = pdfReportGenerator;
    }
Run Code Online (Sandbox Code Playgroud)

这种方法也允许虱子耦合代码,因为我们可以将 IReportGenerator 移动到应用程序的核心,并具有将在更高级别声明的子接口。


T B*_*own 6

我只是简单地注入一个IEnumerable

Startup.cs中的ConfigureServices

Assembly.GetEntryAssembly().GetTypesAssignableFrom<IService>().ForEach((t)=>
                {
                    services.AddScoped(typeof(IService), t);
                });
Run Code Online (Sandbox Code Playgroud)

服务文件夹

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

public class ServiceA : IService
{
    public string Name { get { return "A"; } }
}

public class ServiceB : IService
{    
    public string Name { get { return "B"; } }
}

public class ServiceC : IService
{    
    public string Name { get { return "C"; } }
}
Run Code Online (Sandbox Code Playgroud)

MyController.cs

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

Extensions.cs

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

  • 这不是一个正确的解决方案,因为它在注入“IEnumerable&lt;IService&gt;”时创建每个服务的实例(在本示例中实现“IService”)。如果服务有自己的依赖项,那么创建的对象列表可能会非常大。换句话说,您将创建甚至不使用的对象的实例,这将浪费内存并给 GC 带来压力。 (2认同)

Jav*_*ier 6

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

\n

您可以使用通用接口

\n
public interface IService<T> where T : class {}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
services.AddTransient<IService<ServiceA>, ServiceA>();\nservices.AddTransient<IService<ServiceB>, ServiceB>();\n
Run Code Online (Sandbox Code Playgroud)\n

之后,您必须声明依赖项,如下所示:

\n
private readonly IService<ServiceA> _serviceA;\nprivate readonly IService<ServiceB> _serviceB;\n\npublic WindowManager(IService<ServiceA> serviceA, IService<ServiceB> serviceB)\n{\n    this._serviceA = serviceA ?? throw new ArgumentNullException(nameof(serviceA));\n    this._serviceB = serviceB ?? throw new ArgumentNullException(nameof(ServiceB));\n}\n
Run Code Online (Sandbox Code Playgroud)\n


Jak*_*den 6

模块化扩展类解决方案

答案很晚,但这就是我这样做的方式,它比这个问题的其他一些解决方案有一些优势。

优点:

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

配置示例:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    // default instantiation:
    services.AddKeyedService<IService, ImplementationA, string>("A", ServiceLifetime.Scoped);

    // using an implementation factory to pass a connection string to the constructor:
    services.AddKeyedService<IService, ImplementationB, string>("B", x => {
        var connectionString = ConfigurationManager.ConnectionStrings["mongo"].ConnectionString;
        return new ImplementationB(connectionString);
    }, ServiceLifetime.Scoped);

    // using a custom delegate instead of Func<TKey, TService>
    services.AddKeyedService<IService, ImplementationC, string, StringKeyedService>(
        "C", (_, x) => new StringKeyedService(x), ServiceLifetime.Singleton);

    return services.BuildServiceProvider();
}

public delegate IService StringKeyedService(string key);
Run Code Online (Sandbox Code Playgroud)

用法示例:

public ExampleClass(Func<string, IService> keyedServiceFactory, StringKeyedService<IService> keyedServiceDelegate)
{
    var serviceKey = Configuration.GetValue<string>("IService.Key");
    var service = keyedServiceFactory(serviceKey);
    var serviceC = keyedServiceDelegate("C");
}
Run Code Online (Sandbox Code Playgroud)

执行:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;

public static class KeyedServiceExtensions
{
    // Use this to register TImplementation as TService, injectable as Func<TKey, TService>.
    // Uses default instance activator.
    public static IServiceCollection AddKeyedService<TService, TImplementation, TKey>(this IServiceCollection services, TKey key, ServiceLifetime serviceLifetime)
        where TService : class
        where TImplementation : class, TService
    {
        services.AddTransient<TImplementation>();

        var keyedServiceBuilder = services.CreateOrUpdateKeyedServiceBuilder<TKey, TService, Func<TKey, TService>>(
            DefaultImplementationFactory<TKey, TService>, serviceLifetime);
        keyedServiceBuilder.Add<TImplementation>(key);

        return services;
    }

    // Use this to register TImplementation as TService, injectable as Func<TKey, TService>.
    // Uses implementationFactory to create instances
    public static IServiceCollection AddKeyedService<TService, TImplementation, TKey>(this IServiceCollection services, TKey key,
        Func<IServiceProvider, TImplementation> implementationFactory, ServiceLifetime serviceLifetime)
        where TService : class
        where TImplementation : class, TService
    {
        services.AddTransient(implementationFactory);

        var keyedServiceBuilder = services.CreateOrUpdateKeyedServiceBuilder<TKey, TService, Func<TKey, TService>>(
            DefaultImplementationFactory<TKey, TService>, serviceLifetime);
        keyedServiceBuilder.Add<TImplementation>(key);

        return services;
    }

    // Use this to register TImplementation as TService, injectable as TInjection.
    // Uses default instance activator.
    public static IServiceCollection AddKeyedService<TService, TImplementation, TKey, TInjection>(this IServiceCollection services, TKey key,
        Func<IServiceProvider, Func<TKey, TService>, TInjection> serviceFactory, ServiceLifetime serviceLifetime)
        where TService : class
        where TImplementation : class, TService
        where TInjection : class
    {
        services.AddTransient<TImplementation>();

        var keyedServiceBuilder = services.CreateOrUpdateKeyedServiceBuilder<TKey, TService, TInjection>(
            x => serviceFactory(x, DefaultImplementationFactory<TKey, TService>(x)), serviceLifetime);
        keyedServiceBuilder.Add<TImplementation>(key);

        return services;
    }

    // Use this to register TImplementation as TService, injectable as TInjection.
    // Uses implementationFactory to create instances
    public static IServiceCollection AddKeyedService<TService, TImplementation, TKey, TInjection>(this IServiceCollection services, TKey key,
        Func<IServiceProvider, TImplementation> implementationFactory, Func<IServiceProvider, Func<TKey, TService>, TInjection> serviceFactory, ServiceLifetime serviceLifetime)
        where TService : class
        where TImplementation : class, TService
        where TInjection : class
    {
        services.AddTransient(implementationFactory);

        var keyedServiceBuilder = services.CreateOrUpdateKeyedServiceBuilder<TKey, TService, TInjection>(
            x => serviceFactory(x, DefaultImplementationFactory<TKey, TService>(x)), serviceLifetime);
        keyedServiceBuilder.Add<TImplementation>(key);

        return services;
    }

    private static KeyedServiceBuilder<TKey, TService> CreateOrUpdateKeyedServiceBuilder<TKey, TService, TInjection>(this IServiceCollection services,
        Func<IServiceProvider, TInjection> serviceFactory, ServiceLifetime serviceLifetime)
        where TService : class
        where TInjection : class
    {
        var builderServiceDescription = services.SingleOrDefault(x => x.ServiceType == typeof(KeyedServiceBuilder<TKey, TService>));
        KeyedServiceBuilder<TKey, TService> keyedServiceBuilder;
        if (builderServiceDescription is null)
        {
            keyedServiceBuilder = new KeyedServiceBuilder<TKey, TService>();
            services.AddSingleton(keyedServiceBuilder);

            switch (serviceLifetime)
            {
                case ServiceLifetime.Singleton:
                    services.AddSingleton(serviceFactory);
                    break;
                case ServiceLifetime.Scoped:
                    services.AddScoped(serviceFactory);
                    break;
                case ServiceLifetime.Transient:
                    services.AddTransient(serviceFactory);
                    break;
                default:
                    throw new ArgumentOutOfRangeException(nameof(serviceLifetime), serviceLifetime, "Invalid value for " + nameof(serviceLifetime));
            }
        }
        else
        {
            CheckLifetime<KeyedServiceBuilder<TKey, TService>>(builderServiceDescription.Lifetime, ServiceLifetime.Singleton);

            var factoryServiceDescriptor = services.SingleOrDefault(x => x.ServiceType == typeof(TInjection));
            CheckLifetime<TInjection>(factoryServiceDescriptor.Lifetime, serviceLifetime);

            keyedServiceBuilder = (KeyedServiceBuilder<TKey, TService>)builderServiceDescription.ImplementationInstance;
        }

        return keyedServiceBuilder;

        static void CheckLifetime<T>(ServiceLifetime actual, ServiceLifetime expected)
        {
            if (actual != expected)
                throw new ApplicationException($"{typeof(T).FullName} is already registered with a different ServiceLifetime. Expected: '{expected}', Actual: '{actual}'");
        }
    }

    private static Func<TKey, TService> DefaultImplementationFactory<TKey, TService>(IServiceProvider x) where TService : class
        => x.GetRequiredService<KeyedServiceBuilder<TKey, TService>>().Build(x);

    private sealed class KeyedServiceBuilder<TKey, TService>
    {
        private readonly Dictionary<TKey, Type> _serviceImplementationTypes = new Dictionary<TKey, Type>();

        internal void Add<TImplementation>(TKey key) where TImplementation : class, TService
        {
            if (_serviceImplementationTypes.TryGetValue(key, out var type) && type == typeof(TImplementation))
                return; //this type is already registered under this key

            _serviceImplementationTypes[key] = typeof(TImplementation);
        }

        internal Func<TKey, TService> Build(IServiceProvider serviceProvider)
        {
            var serviceTypeDictionary = _serviceImplementationTypes.Values.Distinct()
                .ToDictionary(
                    type => type,
                    type => new Lazy<TService>(
                        () => (TService)serviceProvider.GetRequiredService(type),
                        LazyThreadSafetyMode.ExecutionAndPublication
                    )
                );
            var serviceDictionary = _serviceImplementationTypes
                .ToDictionary(kvp => kvp.Key, kvp => serviceTypeDictionary[kvp.Value]);

            return key => serviceDictionary[key].Value;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

也可以在此基础上制作一个流体界面,如果有人对此感兴趣,请告诉我。

液体使用示例:

var keyedService = services.KeyedSingleton<IService, ServiceKey>()
    .As<ICustomKeyedService<TKey, IService>>((_, x) => new CustomKeyedServiceInterface<ServiceKey, IService>(x));
keyedService.Key(ServiceKey.A).Add<ServiceA>();
keyedService.Key(ServiceKey.B).Add(x => {
    x.GetService<ILogger>.LogDebug("Instantiating ServiceB");
    return new ServiceB();
});
Run Code Online (Sandbox Code Playgroud)

  • KeyedSingleton 将在 .net 8 中可用于 Microsoft.Extensions.DependencyInjection (2认同)

小智 6

我有同样的问题,我解决了使用<T>

我的界面:

public interface IProvider<T>
{
    Task<string> GetTotalSearchResults(string searchValue);
}
Run Code Online (Sandbox Code Playgroud)

我的服务配置:

var host = Host.CreateDefaultBuilder()
                .ConfigureServices((_, services) =>
                {
                    services.AddSingleton(googleSettings);
                    services.AddSingleton(bingSettings);
                    services.AddSingleton<IProvider<BingProvider>, BingProvider>();
                    services.AddSingleton<IProvider<GoogleProvider>, GoogleProvider>();
                    services.AddSingleton<ISearchManager, SearchManager>();
                });
Run Code Online (Sandbox Code Playgroud)

您可以在课堂上使用它:

public class SearchManager : ISearchManager
    {
        private readonly IProvider<BingProvider> _bing;
        private readonly IProvider<GoogleProvider> _google;

        public SearchManager(IProvider<BingProvider> bing, IProvider<GoogleProvider> google)
        {
            _bing = bing;
            _google = google;
        }
Run Code Online (Sandbox Code Playgroud)

  • 缺点是这需要在您需要的任何地方指定具体类型,而不是在一个地方。 (2认同)

小智 5

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

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}
Run Code Online (Sandbox Code Playgroud)

创建您的各种实现

public class YourClassA : IStage<YouClassA> { 
    public void DoSomething() 
    {
        ...TODO
    }
}

public class YourClassB : IStage<YourClassB> { .....etc. }
Run Code Online (Sandbox Code Playgroud)

登记

services.AddTransient<IStage<YourClassA>, YourClassA>()
services.AddTransient<IStage<YourClassB>, YourClassB>()
Run Code Online (Sandbox Code Playgroud)

构造函数和实例用法...

public class Whatever
{
   private IStage ClassA { get; }

   public Whatever(IStage<YourClassA> yourClassA)
   {
         ClassA = yourClassA;
   }

   public void SomeWhateverMethod()
   {
        ClassA.DoSomething();
        .....
   }
Run Code Online (Sandbox Code Playgroud)