【问题标题】:Get the container instance for Simple Injector获取 Simple Injector 的容器实例
【发布时间】:2013-07-28 08:28:06
【问题描述】:

我正在将 Simple Injector 与 ASP.NET MVC 项目一起使用。我添加了SimpleInjector.Integration.Web.Mvc nuget 包。这会在App_Start 文件夹中添加SimpleInjectorInitializer 类并初始化DI。代码看起来像

public static void Initialize()
{
    // Did you know the container can diagnose your configuration? 
    // Go to: https://simpleinjector.org/diagnostics
    var container = new Container();

    //Container configuration code
    DependencyResolver.SetResolver(
        new SimpleInjectorDependencyResolver(container));
}

这会正确配置 MVC 控制器的 DI。

我的问题是,如果我想在任何控制器\类中获取容器的实例来手动解决一些依赖关系,我该怎么做。

我之前在 AutoFac 上工作过,它有一个依赖接口 IComponentContext,可以将它注入到需要手动进行任何解析的任何类中。

更新

这是一个场景。我的控制器使用了一个服务,该服务的初始化依赖于控制器方法中传递的输入参数,因此在构建期间无法实例化依赖项。

我知道这对 DI 来说有点反模式,但它在少数地方是必需的,因此注入 DI 容器是下一个最好的方法。简单的 Injector 示例应该使用静态变量来共享我想要避免的容器,而且 SimpleInjectorInitializer 的工作方式也是不可能的。

【问题讨论】:

  • 你可以注入容器,但你不应该。在这种情况下,您将容器作为service locator 滥用。为什么需要该控制器中的容器?
  • 添加需要支持的场景

标签: c# asp.net-mvc dependency-injection simple-injector


【解决方案1】:

除了作为应用程序启动路径一部分的任何代码外,任何代码都不应直接依赖于容器(或容器抽象、容器外观等)。这种模式被称为Service LocatorMark Seemann 有一个good explanation 为什么这是一个坏主意。

因此组件(例如控制器)不应直接依赖于容器,因为这会隐藏使用的依赖项并使类更难测试。此外,您的代码开始依赖于外部框架(使其更难更改)或依赖于它不需要知道的抽象。

我的控制器使用了一个初始化依赖于输入的服务 在控制器方法中传递的参数,因此依赖 在构建期间无法实例化

这个问题有一个通用模式:abstract factory design pattern。工厂模式允许您延迟类型的创建,并允许您传递额外的运行时参数以构建特定类型。当你这样做时,你的控制器不必依赖于 Container 并且它可以防止你在单元测试中传递一个构造的容器(DI 框架通常不应该在你的单元测试项目中使用)。

但请注意,让您的组件需要runtime data during creation is a code smell。防止这样做。

您可能认为这样做只是将问题转移到工厂实现。虽然我们正在将容器的依赖转移到工厂实现中,但实际上我们正在解决问题,因为工厂实现将成为应用程序Composition Root 的一部分,这使得应用程序代码本身可以忽略任何 DI 框架。

这就是我建议您构建代码的方式:

// Definition of the factory in the UI or BL layer
public interface ISomeServiceFactory
{
    ISomeService Create(int inputParameter);
}

// Controller depending on that factory:
public class MyController : Controller
{
    private readonly ISomeServiceFactory factory;

    public MyController(ISomeServiceFactory factory)
    {
        this.factory = factory;
    }

    public ActionResult Index(int value)
    {
        // here we use that factory
        var service = this.factory.Create(value);
    }
}

在您的组合根目录(启动路径)中,我们定义工厂实现及其注册:

private class SomeServiceFactory : ISomeServiceFactory
{
    private readonly Container container;

    // Here we depend on Container, which is fine, since
    // we're inside the composition root. The rest of the
    // application knows nothing about a DI framework.
    public SomeServiceFactory(Container container)
    {
        this.container = container;
    }

    public ISomeService Create(int inputParameter)
    {
        // Do what ever we need to do here. For instance:
        if (inputParameter == 0)
            return this.container.GetInstance<Service1>();
        else
            return this.container.GetInstance<Service2>();
    }
}

public static void Initialize()
{
    var container = new Container();

    container.RegisterSingle<ISomeServiceFactory, SomeServiceFactory>();
}

在创建时,Container 会自行注册(使用调用 RegisterSingle&lt;Container&gt;(this)),因此您始终可以将容器注入任何组件。这类似于在使用 Autofac 时注入IComponentContext。但同样适用于 Autofac、Simple Injector 和任何其他容器:您不想将容器注入位于组合根之外的组件中(而且几乎没有理由这样做)。

【讨论】:

  • 谢谢史蒂夫,是的,我确实理解这部分,并且确实应用了与您在此处解释的相同模式。但由于 Factory 本身需要容器,所以我使用 AutoFac IComponentContext。我对如何在 SimpleInjector 中做同样的事情感到困惑。
  • 除了 Steven 的出色回答之外,我们可以通过使用以下代码轻松地使用泛型返回 ISomeService 的具体具体实现(而不是使用 if/else 进行硬编码):public T Create&lt;T&gt;() where T : ISomeService { return (T)this.container.GetInstance(typeof(T)); }。然后使用:var service = this.factory.Create&lt;Service1&gt;();。您还需要使用以下内容更新ISomeServiceFactory 接口:T Create&lt;T&gt;() where T : ISomeService;
  • 从理论上讲,为您的代码创建的所有类型创建工厂接口是一个好主意。但实际上,可能有一些代码您必须创建许多不同类型的对象。然后,您可以使用一个实际上可以为您创建许多不同类型的接口。一些原教旨主义者会因此而讨厌我,但是,IContainer。
猜你喜欢
  • 1970-01-01
  • 2014-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-23
  • 1970-01-01
  • 1970-01-01
  • 2017-03-20
相关资源
最近更新 更多