如何访问 ASP.NET MVC 控制器中的 Simple Injector 容器?

How to access Simple Injector container inside ASP.NET MVC controller?

我想在 Simple Injector 中尝试一下混合生活方式。我想要一个可选的显式定义的异步范围,并在未定义异步范围的情况下回退到 Web 请求范围。

我打算在 ASP.NET MVC 控制器中使用它,通常我想根据最外层的 Web 请求范围解析实例,但在某些情况下,我想在内部显式创建一个异步范围控制器的动作,并且范围内的实例的生命周期更短。

要在控制器的操作中显式创建异步作用域,我必须这样做:

using (AsyncScopedLifestyle.BeginScope(container))
{
    // ...
}

但是,我的控制器中没有 container 引用,因为它都是用 DependencyResolver 设置的(我使用的是 ASP.NET MVC 5)。


我的问题是:在 ASP.NET MVC 控制器中访问 Simple Injector 容器的最佳方式是什么?

是否应该将容器实例本身注册为服务,并在控制器中通过构造函数注入获取?或者我是否围绕它创建一些包装器(例如 ISimpleInjectorContainerProvider)并从那里获取它?或者有什么方法可以通过 DependencyResolver?

抱歉,如果这个问题有点愚蠢,我的目标只是避免做一些可能产生负面影响的不良做法。

What is the best way of accessing Simple Injector container inside ASP.NET MVC controller?

在控制器中访问容器的最佳方式是不要访问。

应用程序中唯一应该访问 DI 容器或其抽象(例如服务定位器或 DependencyResolver)的地方是 Composition Root.

然而,

MVC 控制器 不是 组合根的一部分,它们是表示层的一部分。根据 DI 容器或 任何 与之相关的构造是一个坏主意。

相反,通过使用 Abstractions,依赖注入为我们提供了一种使用 decorators.

拦截方法调用的方法

这允许您在组合根 内部 定义装饰器,并在将调用转发到包装服务之前让它控制范围。

比如说你的 OrderController 依赖于一个 IOrderService 如下:

public class OrderController : Controller
{
    private readonly IOrderService service;

    public ProductController(IOrderService service)
    {
        this.service = service;
    }

    [Post]       
    public ActionResult CancelOrder(Guid orderId)
    {
        this.service.CancelOrder(orderId);

        return this.Ok();
    }
}

万一 CancelOrder 操作需要 运行 在它自己的隔离范围内,而不是让 OrderController 处理这个,我们应该把这个责任转移到另一个 class.由于将它移到 OrderService 实现中也是一个坏主意,我们可以将此行为放在装饰器中:

// This class depends on the Container and should therefore be part of
// your Composition Root
public class ScopingOrderServiceDecorator : IOrderService
{
    // Depend on the Container
    private readonly Container container;

    // Wrap a Func<IOrderService>. This allows the 'real' `IOrderService`
    // to be created by the container within the defined scope.
    private readonly Func<IOrderService> decorateeFactory;

    ScopingOrderServiceDecorator(Container container, Func<IOrderService> decorateeFactory)
    {
        this.container = container;
        this.decorateeFactory = decorateeFactory;
    }

    public void CancelOrder(Guid orderId)
    {
        // Create a new scope
        using (AsyncScopedLifestyle.BeginScope(this.container))
        {
            // Within the scope, invoke the factory. That ensures an instance
            // for this scope.
            IOrderService decoratee = this.decorateeFactory.Invoke();

            // Forward the call to the created decoratee
            decoratee.CancelOrder(orderId);
        }
    }
}

通过使用装饰器,您可以保持控制器和实际业务逻辑不变,并允许您仅通过更改组合根来添加此行为。

您可以在 Simple Injector 中注册,如下所示:

container.Register<IOrderService, OrderService>();
container.RegisterDecorator<IOrderService, ScopingOrderServiceDecorator>();

简单注入器在看到 SimpleInjector.Container 类型的构造函数参数时会自动将自身注入到构造函数中。您没有为此进行任何特殊注册。但是,如上所述,只有 class 是 的一部分 你的组合根应该依赖 Container,所以不要散布 Container 遍及您的代码库。这将导致代码难以维护和测试。

Simple Injector 认为 Func<IOrderService> 是一种特殊的依赖。通常,Simple Injector 不会自动注入 Func<T> 依赖项,但装饰器是此规则的例外。简单注入器确保注入的 Func<T> 可以解析 'real' 实现。