The Subtle Perils of Controller Dependency Injection in ASP.NET Core MVC

Some time ago I wrote a blog about how ASP.NET MVC 6 discovers controllers. While a lot has change since then, including the name – now the framework being called ASP.NET Core MVC, the post is still valid and the processes described there haven’t really changed.

That said, there is one extra thing that should be added to it, and that is how external dependency injection containers relate to the process of controller discovery and instantiation, as there is a subtle difference between ASP.NET Core MVC and the “classic” frameworks – MVC 5 or Web API 2. This post is really sparked by the conversation on Twitter with Jeremy and Kristian.

Who creates instances of the controllers?

So in the “classic” frameworks – MVC 5 and Web API 2, you’d plug in an external container by registering a custom IDependencyResolver, which is an adapter between the framework and a 3rd party DI container. Once that’s registered, the framework’s default controller activator would always try to create an instance of the controller from the container, and only fallback to creating the instance manually (using a type activator service).

That default behavior is not preserved in ASP.NET Core MVC and can lead to some confusion. Consider the following snippet of code, which wires in Autofac (using Autofac.Extensions.DependencyInjection package from NuGet) into an ASP.NET Core application:

In the above listing, we are configuring IServiceProvider to be resolved from Autofac container (effectively setting up an Autofac service adapter). It is then used through the framework to resolve dependencies for controllers and is also exposed on the HttpContext for any other use cases as (ugh) a service locator.

So we can now do this:

When the framework (via a service called DefaultControllerActivator) will create an instance of a controller, it will resolve all of its constructor dependencies from the IServiceProvider – which in our case will be an Autofac specific one. However, the subtle difference between this behavior and what we are used to from Web API 2 and MVC 5, is that the controller itself will not be attempted to be resolved from the container, only its constructor parameters.

Why is this important? Let’s go back to the Autofac set up code, and make it a bit more interesting – and leverage some of the more advanced features of Autofac such as property injection (forget the religious discussion whether property injection is good or bad, it’s just an example of trying to use a container-specific feature).

So our set up code now looks like this:

As a result, since we now support properties injection, our controller can be modified to look like this:

Makes sense, right? Unfortunately, the problem is that this code is not going to work here. When you run this API, the request is gonna hit the action, but the FooService will be null – even though we explicitly instructed Autofac to use property injection.

The reason for this is something that we already explained. While controller’s constructor dependencies would be resolved by MVC from the IServiceProvider (so in our case an Autofac adapter), the instance of the controller itself (and its disposal too) is created and owned by the framework, not by the container.

Making MVC resolve controllers from the container

So in order to fix this, you can do two things. You can use an extension method to force MVC to treat “controllers as services”, meaning that MVC will now attempt to resolve controllers from the IServiceProvider, and also let the container managed their lifetime scope.

This can be done by changing our Autofac set up to (key change is to use AddControllersAsServices method) the following:

It is kind of gonna work, but only partially. After this change, the controllers are indeed going to be resolved from Autofac, but as you probably noticed, we lost the possibility to configure any Autofac specific features against them, so we lost the property injection again. Our above change relies on calling AddControllersAsServices which internally does 3 things:

  • sets up a fixed-set StaticControllerTypeProvider based on what we have passed into AddControllersAsServices
  • replaces the DefaultControllerActivator with ServiceBasedControllerActivator (meaning the framework will now attempt to resolve controller instances from IServiceProvider)
  • registers every single discovered controller instance into services collection (so into Autofac container in our case)

So we are almost there – but if we want to use some specific DI container features against our controllers, you can rewrite the code into this:

So let’s recap what we did here – we created manually a StaticAssemblyProvider and pointed it to the assembly from which we wanna discover controllers. We then used DefaultControllerTypeProvider to discover them for us (to avoid hardcoding any controller discovery logic). Once we have these two services, we register them as singleton services, in place of the default ones. Then we replace the the DefaultControllerActivator with ServiceBasedControllerActivator so that MVC will start resolving the controllers from Autofac.

Finally, we use the controller types discovered by DefaultControllerTypeProvider and register them in Autofac manually – additionally enabling property injection (or, at this point, any other advanced feature of a DI container we’d like to use).

And that’s it, we now have controllers from a DI container, leveraging any container specific feature, and we have not broken any MVC internals in the process.

Be Sociable, Share!

  • Dave Van den Eynde

    So then, why is this not the default?

  • http://chadly.net/ Chad Lee

    Wow, that seems like a lot of extra code for (what seems to me) to be a common use case.

  • http://trycatchfail.com/ Matt Honeycutt

    Thanks for the well-written article! Unfortunately, and this is no slight on you, this looks like yet another example of things becoming more work and more code in ASP.NET Core, rather than less. I plan to have all this baked in and hidden with the frameworks I build on top of ASP.NET Core, but it feels like something I shouldn’t need to do. >:(

  • Lee Oades

    Great article, and very useful. I can imagine wasting a lot of time trying to work out why this wasn’t working. That said, if we discount property injection as a bad pattern, what other container benefits are gained by doing this? The lifetime controls (e.g. child containers) don’t apply here as they would in say a WPF app. I’m assuming injection of auto factories still works because that’s on the ctor e.g. Func

  • vncastanheira

    Why do this while you could just decorate FooService with [FromServices] attribute?

    [FromServices]
    public IFooService FooService { get; set; }

    • http://www.strathweb.com/ Filip W

      [FromServices] no longer works on controller properties https://github.com/aspnet/Mvc/issues/3578 so using an IoC container directly is the only way to get this feature.

      Nevertheless, like I said, it was merely an example of using a more advanced IoC container feature, could have used any other too (i.e. keyed services, multitenancy, type interceptors etc)

  • Pingback: The week in .NET – 4/5/2016 | 神刀安全网()

  • Pingback: The week in .NET – 4/5/2016 | Tech News and Blog()

  • Drazen Dotlic

    Great article!
    Unfortunately, the code does not work due to (again) breaking changes in RC2 (sigh).

    Browsing through the source of RC2 trying to figure out what to change… will post back if (or rather when) I do.

  • Drazen Dotlic

    So, here’s what works for RC2, for the other poor souls struggling with this (formatting slightly off, sorry don’t know how to fix it)

  • http://peter.grman.at/ Peter Grman

    Hi, great article – unfortunately it doesn’t work anymore due to https://github.com/aspnet/Mvc/commit/f638c05 Are there any plans to update it? Thx

  • Patrick

    All the samples l’ve found show how to register all controllers in an assembly. Does anyone know who to register just one controller in an assembly (specify one particular type)?
    Thanks heaps.

  • Bennie Kloosteman

    Controller lifetime and dispose and how it relates to middleware in mvc core are different , if you treat them as services and want autofac to take care of it you need a very good understanding of these mechanisms.

    I also dont think its a common use case – the common usecase in to use the out of the box dependency injection. I have tried others but it aint worth it .

  • Marc L

    Read together with the Autofac documentation, this is really confusing and contradictory to me. Plus, there is some coding style I haven’t seen before. Some questions:
    Is this (or Drazen’s code below) still necessary?
    Why resolve the IServiceProvider from the container (resolving the container from the container) instead of just returning “new AutofacServiceProvider(container);”?
    Following on that, why are you not preserving the container instance for later disposal? Is that not necessary?
    Finally, besides the situation where a controller needs specialized container features for dependency resolution, what are the advantages/disadvantages of letting ASP.NET Core control controller lifetime rather than Autofac (or other container of choice, I suppose)?
    I feel like I’m missing something, or that the documentation hasn’t caught up with reality yet.