If you are programming with ASP.NET MVC, you know that the controller’s should have a suffix of “Controller” such as “HomeController”. You may also know that this is how DefaultControllerFactory works.
Let’s look into the technical details. I will skip some unrelated facts, but we will check the MVC source code, and modify it a little. Please go to CodePlex and download the MVC source code.
When a request comes into IIS (Microsoft’s web server application), IIS first checks the request, then the MVCRouteHandler is called (assuming this is an MVC request). The MVCRouteHandler creates an instance of MvcHandler by passing the request context. You can check out the code in the MVCRouteHandler.cs file.
protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) {
return new MvcHandler(requestContext);
}
After creating the instance, ProcessRequest (from MVCHandler) is invoked and HttpContext is passed as the parameter. The ProcessRequest function is below:
protected internal virtual void ProcessRequest(HttpContextBase httpContext) {
AddVersionHeader(httpContext);
// Get the controller type
string controllerName = RequestContext.RouteData.
GetRequiredString("controller");
// Instantiate the controller and call Execute
IControllerFactory factory = ControllerBuilder.
GetControllerFactory();
IController controller = factory.CreateController(RequestContext,
controllerName);
.... some other calls
As you can see, this function calls the GetControllerFactory() method, to get the ControllerFactory. If you haven’t registered a new ControllerFactory, this function returns the DefaultControllerFactory. The next call in ProcessRequest function is CreateController. So let’s see what is happening in the DefaultControllerFactory’s CreateController function.
public virtual IController CreateController(RequestContext requestContext,
string controllerName) {
if (requestContext == null) {
throw new ArgumentNullException("requestContext");
}
if (String.IsNullOrEmpty(controllerName)) {
throw new ArgumentException(
MvcResources.Common_NullOrEmpty, "controllerName");
}
RequestContext = requestContext;
Type controllerType = GetControllerType(controllerName);
IController controller = GetControllerInstance(controllerType);
return controller;
}
This function after checking requestContext, and controllerName against null values, calls GetControllerType. Here is what GetControllerType does:
protected internal virtual Type GetControllerType(string controllerName) {
if (String.IsNullOrEmpty(controllerName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty,
"controllerName");
}
// first search in the current route's namespace collection
object routeNamespacesObj;
Type match;
match = GetControllerTypeWithinNamespaces
(controllerName, nsHash);
if (match != null) {
return match;
}
}
}
This is not the whole function but a piece of it.
Again after checking some values against null, it is calling the main function which is GetControllerTypeWithinNamespaces. I am sure you are already bored from one function call to another function call :) however, I will be writing more about the MVC infrastructure, so it is good to show them. So one more time, let’s look at the GetControllerTypeWithinNamespace function.
The first thing this function does is to call: ControllerTypeCache.EnsureInitialized(BuildManager). This will load all the controllers and cache them, so that the next time a controller is needed, the framework doesn’t have to find and load the controllers. This is a simple yet pretty important function.
public void EnsureInitialized(IBuildManager buildManager) {
if (_cache == null) {
lock (_lockObj) {
if (_cache == null) {
List<Type> controllerTypes = GetAllControllerTypes(buildManager);
var groupedByName = controllerTypes.GroupBy(
t => t.Name.Substring(0, t.Name.Length - "Controller".Length),
StringComparer.OrdinalIgnoreCase);
_cache = groupedByName.ToDictionary(
g => g.Key,
g => g.ToLookup(t => t.Namespace ??
String.Empty, StringComparer.OrdinalIgnoreCase),
StringComparer.OrdinalIgnoreCase);
}
}
}
}
As you can see this function caches the controllers after loading them. The _cache variable that is used in this function is a Dictionary<string,ILookup<string,type>>. So basically the framework loads the controllers into a cache. The function that loads the controllers is GetAllControllerTypes. It loads all the assemblies, iterating through them by calling a function: IsControllerType. This is why, this needs to be cached, as you don’t want to load all the assemblies and check if they are controllers. So this IsControllerType function is the reason why we have to add suffix “Controller” to our controllers:
internal static bool IsControllerType(Type t) {
return
t != null &&
t.IsPublic &&
t.Name.EndsWith("Controller",
StringComparison.OrdinalIgnoreCase) &&
!t.IsAbstract &&
typeof(IController).IsAssignableFrom(t);
}
This function is checking if the passed in controller, has a suffix of “Controller” or not. If it does, it returns true, and GetAllControllerTypes adds this controller to the list, and returns the list of controllers.
The call goes back to EnsureInitialized which strips out the Controller suffix, and adds it into the cache. Let’s assume we want our controllers to have suffix of “Volkan” ; what do we need to do?
First you have to change the IsControllerType so that it checks .EndsWith(“Volkan”) not “Controller”. This will let the <controllerName>Volkan controllers to be added to the list. But this is not enough, the next thing we have to do is, go back to EnsureInitialized and change the stripping part from t.Name.Length - "Controller".Length to t.Name.Length - "Volkan".Length .
Finally you have to put the “Volkan” suffix to your controllers, and now this becomes a valid controller:
public class HomeVolkan : Controller{
Enjoy your time debugging the MVC code!
Tags: mvc, defaultcontrollerfactory