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

时间:2018-02-18 12:36:55

标签: c# asp.net-mvc ioc-container simple-injector

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

我打算在ASP.NET MVC控制器中使用它,我通常希望针对最外层的Web请求范围解析实例,但在某些情况下,我想在内部显式创建一个Async Scope控制器的动作,并且范围内的实例的生活方式更短。

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

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

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

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

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

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

1 个答案:

答案 0 :(得分:2)

  

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

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

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

然而,

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

相反,通过使用抽象,依赖注入为我们提供了一种使用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处理这个,我们应该将此职责移到另一个类。由于将其移入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>();

Simple Injector在看到类型SimpleInjector.Container的构造函数参数时会自动在构造函数中注入自身。您没有为此进行任何特殊注册。但是,如上所述,只有属于你的作品根目录的类应该依赖于Container,所以不要在Container遍布你的Func<IOrderService>代码库。这将导致代码难以维护,难以测试。

Simple Injector认为Func<T>是一种特殊的依赖关系。通常,Simple Injector不会自动注入Func<T>依赖项,但装饰器是此规则的例外。 Simple Injector确保注入的'User' object has no attribute 'nazwa_set' 可以解决“真实”问题。实施