如果在为 MVC 4 设置依赖项解析器之前使用验证,简单注入器将失败

Simple Injector fails if using verify before setting dependency resolver for MVC 4

我们有一个 ASP.NET 基于 MVC 4 的应用程序已经有几年的历史了,我正在努力摆脱它的一些技术债务。我正在做的一件事是引入依赖注入,这样我们就可以更好地将业务逻辑与数据访问实现分开,并减少编写隔离单元测试的痛苦。我已经使用了 Simple Injector,但遇到了一些问题。

我关注了 MVC integration guide in the Simple Injector documentation。它描述了这样的初始化过程:

  1. 创建容器
  2. 在容器中注册类型
  3. 验证容器(可选)
  4. 覆盖默认的依赖解析器

到目前为止,这是在应用程序中实现的方式。为清楚起见,我删除了日志记录语句,并为上述步骤添加了标记注释:

// 1
var container = new Container();
var webRequestLifestyle = new WebRequestLifestyle();

// 2
container.Register<IOrganizationService>(
    delegate
    {
        var proxy = new OrganizationServiceProxy(
            organizationServiceManagement, clientCredentials);
        proxy.EnableProxyTypes();
        return proxy;
    },
    webRequestLifestyle);

container.RegisterSingle<ILoggerProvider>(LoggerProvider); // static field
container.Register<IExternalLinkRepository, ExternalLinkRepository>(webRequestLifestyle);
container.Register<IQueueRepository, QueueRepository>(webRequestLifestyle);
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.RegisterMvcIntegratedFilterProvider();

// 3
container.Verify(VerificationOption.VerifyAndDiagnose);

// 4
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));

上述代码在尝试实例化 MVC 控制器时在第 3 步失败,抛出 System.InvalidOperationException: An error occurred when trying to create a controller of type 'MyProject.MyNamespace.MyController'. Make sure that the controller has a parameterless public constructor.

这是有道理的,因为我的控制器是为构造函数注入设置的。例如:

public MyController(ILoggerProvider loggerProvider)
{
    Logger = loggerProvider.Get(GetType());
}

默认的 MVC 控制器激活器不知道如何处理这个。但是,我不明白的是为什么 Simple Injector 的 Container.Verify 方法完全命中了 MVC 的默认控制器激活器。容器不应该天生就使用 Simple Injector 的依赖解析来测试依赖图吗?查看异常调用堆栈,它起源于 System.Web.Mvc.DefaultControllerFactory.DefaultControllerActivator.Create,因此它肯定在某些时候超出了 Simple Injector 的范围。

但是,当我调换第 3 步和第 4 步的顺序时:

DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
container.Verify(VerificationOption.VerifyAndDiagnose);

它成功地验证了容器,并且依赖注入在应用程序中也按预期工作。这似乎暂时解决了问题。不过,我想知道:

  1. 为什么 Simple Injector 使用 MVC 的默认控制器激活器来测试依赖解析?这是expected/documented吗?
  2. 先设置自定义解析器再验证是否有副作用?我问是因为这与文档中的指南相反。它似乎按预期工作,如果它们中的任何一个失败,应用程序无论如何都会崩溃,所以从应用程序的角度来看,这似乎无关紧要。

所以,这是一个值得追踪的有趣事件。作为最后的手段,应用程序尝试使用 Application_Error 处理未捕获的错误。在Application_Start中完成的Verify()方法确实在验证失败时抛出了异常,但是这个被Application_Error方法捕获了(需要搞清楚为什么没有记录,但那是另一回事)。因此,从未进行过对 DependencyResolver.SetResolver 的调用。通过管道的实际请求然后将尝试使用默认控制器激活器。而且发出的请求当然不是向隐藏的控制器发出的,导致验证失败。

一个控制器也有一个静态构造函数,它缓存了一批只读数据,不需要为应用程序的每个新请求处理这些数据。并且该静态构造函数由于错误而崩溃,该错误阻止了该控制器的实例化并使应用程序停止。以更明智的方式缓存数据并删除静态构造函数后,验证正常,应用程序在再次根据指南进行 DI 设置时工作正常。