16

我正在将 Simple Injector 与 ASP.NET MVC 项目一起使用。我添加了SimpleInjector.Integration.Web.Mvcnuget 包。这会SimpleInjectorInitializer在文件夹中添加类App_Start并初始化 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 容器是下一个最好的事情。简单的注射器样本应该使用静态变量来共享我想要避免的容器,而且它也是不可能的SimpleInjectorInitializer

4

1 回答 1

31

除了作为应用程序启动路径一部分的任何代码外,任何代码都不应直接依赖于容器(或容器抽象、容器外观等)。这种模式称为服务定位器Mark Seemann很好地解释了为什么这是一个坏主意。

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

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

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

但是请注意,让您的组件在创建期间需要运行时数据是一种代码异味。防止这样做。

您可能会认为这样做只是将问题转移到工厂实现中。虽然我们正在将容器的依赖转移到工厂实现中,但实际上我们正在解决问题,因为工厂实现将成为应用程序组合根的一部分,这使得应用程序代码本身可以忽略任何 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注册本身(使用 call RegisterSingle<Container>(this)),因此您始终可以将容器注入任何组件。这类似于在IComponentContext使用 Autofac 时注入。但同样适用于 Autofac、Simple Injector 和任何其他容器:您不想将容器注入位于组合根之外的组件中(而且几乎没有理由这样做)。

于 2013-07-28T15:00:17.750 回答