Simple Injector fails if using verify before setti

2019-07-11 18:44发布

问题:

We have an ASP.NET MVC 4 based application that's a couple of years old, and I'm working on ridding it of some technical debt. One of the things I'm doing is introduce dependency injection, so that we can better separate the business logic from the data access implementation, and make it less painful to write isolated unit tests. I've gone with Simple Injector, but I'm having some issues.

I've followed the MVC integration guide in the Simple Injector documentation. It describes the initialization process like this:

  1. Create a container
  2. Register types in the container
  3. Verify the container (optional)
  4. Override the default dependency resolver

So, here's the way this is implemented in the application so far. I've removed logging statements for clarity, and added marker comments for the steps above:

// 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));

The above code fails on step 3 when attempting to instantiate the MVC controllers, throwing 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.

Which makes sense, because my controllers are set up for constructor injection. For example:

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

The default MVC controller activator wouldn't know what to do with this. However, what I don't understand is why the Container.Verify method of Simple Injector hits the default controller activator of MVC at all. Shouldn't the container innately use Simple Injector's dependency resolution to test the dependency graph? Looking through the exception call stack, it originates at System.Web.Mvc.DefaultControllerFactory.DefaultControllerActivator.Create, so it definitely goes outside the bounds of Simple Injector at some point.

However, when I swap the order of steps 3 and 4:

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

It successfully verifies the container, and dependency injection works as expected in the application as well. This seems to fix the problems for now. Still, I'm wondering:

  1. Why does Simple Injector use MVC's default controller activator to test dependency resolution? Is this expected/documented anywhere?
  2. Are there any side effects to setting the custom resolver first, then verifying? I'm asking because this is contrary to the guide found in the documentation. It seems to work as expected, and if either of them fail the application will crash anyway, so from the application's point of view it doesn't seem to matter.

回答1:

So, this was an interesting one to track down. As a last resort the application tries to handle uncaught errors with Application_Error. The Verify() method, which is done in Application_Start, did indeed throw an exception when the verification failed, but this was caught by the Application_Error method (need to figure out why it wasn't logged, but that's a different story). So, the call to DependencyResolver.SetResolver was never made. The actual request coming through the pipeline would then attempt to use the default controller activator instead. And the requests made were of course not made to the hidden-away controller which caused verification to fail.

One controller had a static constructor as well, which cached a batch of read-only data that didn't need to be processed for each new request to the application. And that static constructor crashed due to a bug, which prevented that controller from being instantiated and brought the application to a halt. After caching the data in a more sensible way and removing the static constructor, the verification is going fine and the application works fine while doing the DI setup according to the guide again.