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.

Be Sociable, Share!

  • Pingback: Dew Drop – April 1, 2015 (#1985) | Morning Dew()

  • Joel Mueller

    “How ASP.NET MVC 6 discovers controllers” is a statement. No question mark required. For a question mark to be appropriate, it should be phrased as an actual question. “How does ASP.NET MVC 6 discover controllers?”

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1833()

  • Simon Timms

    Ah that’s interesting. I had passively wondered about how the controller discovery worked. Seems like a good balance between exposing things you didn’t mean to and making discovery difficult. Thanks for writing

  • Dilyan Dimitrov

    Very useful information here. It helped me. Thank you for sharing it.

  • Marcos Paulo Honorato

    I’m doing some test with MVC 6 (“Microsoft.AspNet.Mvc”: “6.0.0-beta4″).

    I made a Roles Manager (CRUD), however, when I remove a related roles with a User, it is still accessing the Controller, which in this case has [Authorize (Roles = “Admin”)].

  • http://tamangbinod.com Bakamaru Tamang

    how to inject other dependencies on that assembly? I tried with external project dll but following error message was shown. System.IO.FileNotFoundException

    Could not load file or assembly
    ‘file:///C:UsersbdtamDocumentsVisual Studio
    2015ProjectsWebApplication4srcWebApplication4plugindnx451embeddedview.dll’
    or one of its dependencies. The system cannot find the file specified.

    Ive used this code

    services.AddMvc().WithControllersAsServices(new[]
    {
    Assembly.LoadFrom(_appEnviroment.ApplicationBasePath + @”\plugins\plugin1\embeddedview.dll”)
    });

    • Alexsandro

      Are you have success?

  • Shafqat Ali

    I have controller defined in other dll, when i refer the dll into my MVC6 webproject, it does not load any of controller/routes automaticall. Any idea?

    • Alexsandro

      Good question, are you trying to build a mult tenant app?

  • Andriy Tolstoy

    I think the signature of the AddControllersAsServices method was changed, so use AddApplicationPart + AddControllersAsServices instead:

    services
    .AddMvc()
    .AddApplicationPart(typeof(MyController).Assembly)
    .AddApplicationPart(typeof(ExternalPocoController).Assembly)
    .AddControllersAsServices();