How ASP.NET MVC 6 discovers controllers?

In the past I did a couple of blog posts (here and here) about how ASP.NET Web API discovers controllers.

ASP.NET MVC 6 supports both regular controllers (inheriting from Controller base type) and POCO controllers. Let’s have a look at how the discovery of them happens in ASP.NET MVC 6. Note that the code and mechanisms discussed in this article were introduced after ASP.NET 5 beta3 was released, so it is not yet available if you use the version of ASP.NET 5 bundled with Visual Studio 2015 CTP6.

Assemblies

Before we even get into looking at the Types that can become controllers, we need to understand how MVC selects assemblies it should look at.

Out of the box, this process is goverened by DefaultAssemblyProvider which dictates that MVC will look for controllers in any assembly that references any of the following MVC assemblies:

  • “Microsoft.AspNet.Mvc”,
  • “Microsoft.AspNet.Mvc.Core”,
  • “Microsoft.AspNet.Mvc.ModelBinding”,
  • “Microsoft.AspNet.Mvc.Razor”,
  • “Microsoft.AspNet.Mvc.Razor.Host”,
  • “Microsoft.AspNet.Mvc.TagHelpers”

This means that if you build an external library in which you want to have your controllers, they will be automatically discovered in any MVC application, since your external library will likely have a reference to “Microsoft.AspNet.Mvc” or any other of the assemblies mentioned above.

On the other hand, if you don’t reference any of the MVC assemblies – for example because you want to have external POCO controllers – they will not be discovered.

There are two ways of customizing the process of selecting candidate assemblies. You can implement a custom IAssemblyProvider (or override the DefaultAssemblyProvider). It’s a single method interface, where you can hint MVC which assemblies should be used:

There is a second way of doing it, probably a bit simpler. There is an extension method on IServicesCollection that allows you to specify which assemblies should be inspected:

Under the hood, this replaces DefaultAssemblyProvider with a StaticAssemblyProvider, and as the name suggests, only defines specific assemblies that MVC should use to find controllers.

Types

Once MVC has the assemblies, it will need to determine which Types in those assemblies can become controllers. Similarly to assemblies discovery, this process is governed by a relevant interface – IControllerTypeProvider.

The out-of-the box rules (defined in DefaultControllerTypeProvider, the default implementation of the above interface) for controllers to be discovered are the following:

  • has to be class
  • can’t be abstract
  • has to be public
  • has to be top-level (not nested)
  • can’t be generic
  • has to either derive from Controller base class or end in Controller suffix (for POCOs) and be located in an assembly that references MVC assembly (as mentioned in the previous point)
  • not be decorated with NonControllerAttribute (an opt out from discovery, for example it your class is not intended to be used by MVC but happens to be called in a way that might cause it to become discovered i.e. DomainController)

You could implement the provider yourself to customize this behavior, but just like it was the case for assemblies, there is an extension method that could help you to restrict MVC to a fixed set of controllers only and not discover anything by convention.

Under the hood, this replaces DefaultControllerTypeProvider with a StaticControllerTypeProvider, and as the name suggests, only defines specific (fixed set) controller types to be used as controllers.