【问题标题】:How to get different instances of same service from IServiceCollection如何从 IServiceCollection 获取相同服务的不同实例
【发布时间】:2022-01-10 14:30:17
【问题描述】:

我有一个 Bar 类,它被定义为 this

public class Bar: IBar
{
    private readonly IFoo _foo;
    private readonly string _keyname;

    public Bar(IFoo, string keyName)
    {
        //assign class field
    }
}

键名字符串区分 Bar 的对象。 Bar 的方法使用这个键名从类外部获取不同的设置/配置。

我希望类的客户端在单个类中具有不同的 Bar 实例。如果我们不使用服务,客户端可以这样做

var bar1 = new Bar(foo, "key1");//instantiate with new
var bar2 = new Bar(foo, "key2");

为了注册 Bar 类,我创建了一个类似这样的扩展方法

public static IServiceCollection AddMyService(this IServiceCollection services, string keyName)
{
    services.AddSingleton<IFoo, Foo>();
    services.AddTransient<IBar>(sp => 
       ActivatorUtilities.CreateInstance<Bar>(sp, keyName)
    );
    return services;
}

我希望客户端像这样注册多个实例

services.AddMyService("key1");
services.AddMyService("key2");

但我不确定他们将如何通过依赖注入访问他们类中的注册服务。这是解决这个问题的正确方法吗?

【问题讨论】:

  • 除了注入服务本身,您还可以让客户注入一个工厂,然后他们使用密钥来获取服务:var bar1 = _injectedBarFactory.Get("key1")
  • @GoodNightNerdPride 你能在答案中提供解决方案吗

标签: c# .net-core ioc-container


【解决方案1】:

您可以通过注入一个为给定密钥创建IBar 的工厂来解决此问题:

public interface IBarFactory { IBar Create(string key); }

public class BarFactory : IBarFactory {
    private readonly IFoo _foo;
    public BarFactory(IFoo foo) => _foo = foo;
    public IBar Create(string key) => new Bar(_foo, key);
}

客户端代码可以注入这个工厂:

public class Client1 {
    private readonly IBar _bar1;
    public Client1(IBarFactory barFactory)
        => _bar1 = barFactory.Create("key1");
}

注意BarFactory需要注入并转发Bar的所有依赖。

只有IBarFactory 需要在您的 DI 容器中注册。不同的IBars 不需要注册(当然,除了它们的依赖项,如IFoo)。

一个缺点是当您想在Client1 的单元测试中模拟IBar 时需要额外的设置。您必须模拟并设置 IBarFactory 以返回 IBar 的模拟。

【讨论】:

    【解决方案2】:

    dotnet core 自带的依赖注入不允许使用命名服务。

    在您的情况下,您可以做的最简单的事情是注册所有 Bar 服务,然后将它们作为 IEnumerable 注入:

     var factory = ActivationUtilities.CreateFactory(typeof(Bar), new Type[] { typeof(string) });
     services.AddSingleton<IFoo, Foo>();
     services.AddTransient<IBar>(sp => 
       (IBar) factory(sp, new object[] { "key1" })
     );
     
     services.AddTransient<IBar>(sp => 
       (IBar) factory(sp, new object[] { "key2" })
     );
     services.AddTransient<AClient>();
    
     public class AClient {
          private IEnumerable<IBar> _bars;
          public AClient(IEnumerable<IBar> bars)
          {
              _bars = bars
          }
    
          public IBar GetBarByKey(string key) => _bars.FirstOrDefault(x => x.Key == key);
     }
    

    ActivationUtilities.CreateFactory 方法返回一个 ObjectFactory 委托对象,该对象代表一个函数,该函数接受输入 IServiceProvider 和参数列表。通过这种方式,您可以创建对象,并从服务提供者那里获取一些构造参数,同时直接给出一些参数。

    【讨论】:

    • 是的,这可以是解决方案之一。我想提供一种扩展方法,客户端可以使用它来注册不同的对象。可以提供这种设计的解决方案
    猜你喜欢
    • 2020-06-04
    • 1970-01-01
    • 2020-05-18
    • 2018-03-03
    • 1970-01-01
    • 2016-05-26
    • 2020-06-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多