Controller Type Resolution in ASP.NET MVC

By: Jason Bell | Posted on: 23 Nov 2012

In ASP.NET MVC, a request is typically destined to execute a method of a controller. When it comes to defining a controller, thee are requirements and there are conventions. In many an instance, I've seen some conventions described as requirements and some requirements described as conventions. In this article, I aim to describe some of these common misconceptions and clear things up a bit.

Throughout this article, I refer to components within the ASP.NET MVC framework and include code snippets from the ASP.NET MVC 4 RTM source code. During training classes that I conduct, I always encourage students to download the source and use it as the ultimate authority on how things work within ASP.NET MVC.

An HTTP request has arrived at your ASP.NET MVC application. The routing engine has done its job, found a matching route, and handed things off to the route's assigned IHttpHandler. By default, this is a class named MvcHandler. MvcHandler obtains an IControllerFactory and an instance of a class that implements IController. Finding the appropriate implementation of IController and creating the instance is the responsibility of the IControllerFactory.

protected internal virtual void ProcessRequest(HttpContextBase httpContext)
{
  IController controller;
  IControllerFactory factory;
  ProcessRequestInit(httpContext, out controller, out factory);
  try
  {
    controller.Execute(RequestContext);
  }
  finally
  {
    factory.ReleaseController(controller);
  }
}

This is our first requirement. Any controller we create must implement IController. Commonly, your controller will inherit from Controller which inherits from ControllerBase and that class implements IController.

public interface IController
{
  void Execute(RequestContext requestContext);
}

By default, the class used by the framework that implements IControllerFactory is DefaultControllerFactory. The class goes through a series of steps to find and instantiate the correct controller type.

Controllers Folder

This brings us to the first convention that is commonly presented as a requirement which is that all controllers must reside within in the Controllers folder created by Visual Studio when creating a new ASP.NET MVC project. This is in fact not the case. You can easily demonstrate this by creating a controller and placing it directly under the project (or in any other folder you choose) and making a request to the application.

So, what process does DefaultControllerFactory use to locate a controller type? The basic answer is that, with a few assumptions, it looks everywhere it can. We'll get to those assumptions in a moment. As for where it looks, remember that, at runtime, all of the controllers have been compiled into an assembly. Each controller exists in a namespace but the folders used at design-time are irrelevant (by default, a class created in a folder at design-time is placed in a namespace with the same name as the folder but this is not mandatory). Within the GetControllerType method of DefaultControllerFactory, a search is performed starting with the route’s namespace collection. This can be specified when defining a route.

MapRoute(string name, string url, string[] namespaces)

If not found there, the search continues using the application's default namespace collection. This will include the Controllers namespace used by default for controllers created within the Controllers folder. If still not found, DefaultControllerFactory will search every namespace available to the application. You can see this process (with good comments) in the GetControllerType method of DefaultControllerFactory. This means, without any extra configuration, you can create a controller in any namespace you like. You can even create a controllers in a separate class library that your MVC project has a reference to.

Now, what about the assumptions I mentioned earlier? Well, the framework does expect that all of your controllers will have a type name that ends with "Controller"" and a few other things that we can see from a method in the ControllerTypeCache class.

internal static bool IsControllerType(Type t)
{
  return
    t != null &&
    t.IsPublic && 
    t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
    !t.IsAbstract &&
    typeof(IController).IsAssignableFrom(t);
}

I chose to label these as assumptions rather than requirements because this behavior can be modified in a variety of ways including the creation of a custom controller factory. This topic, however, is for another time.

I hope this article has provided you with a bit more insight into how the ASP.NET MVC framework locates a controller type to instantiate based on the controller name provided by the routing system. In future articles, I will discuss other aspects of the ASP.NET MVC pipeline including the process of selecting and invoking the correct controller action.



comments powered by Disqus