作用域/瞬态依赖项注入确保返回接口和实现的相同实例

时间:2019-02-28 09:01:43

标签: c# asp.net-core dependency-injection .net-core

请随时为该问题建议一个更好的标题。我想不出一个好名字来描述问题。

我需要通过启动时的依赖项注入来使类可访问。该类不仅应通过其具体实现可用,而且应通过其实现的接口使用。而且我需要确保它是通过两次注入返回的同一对象实例。

使我成为单例案例的现实世界场景是为接口(IStore)提供抽象的一个小工具,多个保存具体实现的小工具(DBStore,RedisStore)。当我尝试为每个商店实现实施运行状况检查时,可以注入IStore,但不能注入具体的实现。我想使用一些在具体实现中初始化和修改过的变量(这就是为什么两次注入都需要相同的实例)。由于商店(希望)用作单身人士,因此可以正常工作。我并不是说有一种现实世界的范围和过渡方式。我很好奇,如果不是单身人士,那是否有可能。

以下代码描述了我如何通过单例来做到这一点。

导致我找到单例解决方案的方式:

具有此界面:

public interface ITestInterface
{
  string ReturnAString();
  int ReturnAnInt(); 
}

和这个具体的实现

public class TestImplementation : ITestInterface
{
  private int counter = 0;
  public string ReturnAString() {return "a string"; }
  public int ReturnAnInt() { return counter++; }
}

它们在两个(假设)服务中使用。一个希望将接口注入构造函数中,另一个需要具体实现。

在两种情况下都注入相同实例的Startup.ConfigureServices方法的尝试和错误:

尝试1:

// only ITestInterface is injected but not TestImplemenation
services.AddSingleton<ITestInterface, TestImplementation>();

尝试2:

//only TestImplementation is injected (DI does not recognize it implements the Interface)
services.AddSingleton<TestImplementation>();

尝试3:

// both are injected but they are not singleton any more (counters increment independently)
services.AddSingleton<ITestInterface, TestImplementation>();
services.AddSingleton<TestImplementation, TestImplementation>();

尝试4:

TestImplementation instance = new TestImplementation();
services.AddSingleton<ITestInterface>(instance);
services.AddSingleton(instance);
//services.AddSingleton<TestImplementation>(instance);

好吧,在尝试4时,两次注射的实例相同。

现在让我们假设TestImplementation需要注入一些设置(例如连接)。

我可以编写一种扩展方法,用于从配置中获取设置并将其传递给单例实例。

TestImplementation instance = new TestImplementation(Configuration.GetTestSettings());
services.AddSingleton<ITestInterface>(instance);
services.AddSingleton(instance);

那么我将如何使用范围或瞬态的相同设置来实现两次注入都是同一实例?由于我认为我无法在此处手动/编写实例。

3 个答案:

答案 0 :(得分:1)

基本上,您要将一个服务实现类型注册为两个服务协定(具体类+接口)。这是很常见的情况,但是不幸的是,默认的Microsoft DI容器(ServiceCollection)的功能有限,我看到的达到预期效果的唯一方法是使用工厂委托:

services.AddScoped<TestImplementation>();
services.AddScoped<ITestInterface>(s => s.GetRequiredService<TestImplementation>());

尽管这样做可以解决问题(但要花一些额外的运行时费用),但我还是强烈建议您使用功能齐全的DI容器之一,例如Autofac或Ninject

答案 1 :(得分:1)

如果我没记错,Autofac可以通过使用decorators来完成您想要的事情。

但是,由于公司政策的原因,向第三方库添加依赖通常是不希望的,甚至是被禁止的。

在这种情况下,我认为您最好创建一个工厂。例如:

public class TestFactory
{
    public ITestInterface Create(string flavor)
    {
        if (flavor == "concrete")
        {
            return new TestImplementation();
        }
        else
        {
            return new OtherTestImplementation();
        }
    }
}

然后,在您的Startup.cs中,将其注入:

services.AddSingleton<TestFactory>();

请注意,最终ITestInterface服务的生存期将取决于您对Create(...)方法调用结果的引用的存储位置。

答案 2 :(得分:1)

使用Autofac,您将可以使用AsSelf()方法添加接口和实施接口:

container.RegisterType<TestImplementation>.As<ITestInterface>().AsSelf();

有关更多说明,请参见此answer

在您的情况下-将其作为单身人士使用:SingleInstance()

container.RegisterType<TestImplementation>.As<ITestInterface>().AsSelf().SingleInstance();