Category support in ASP.NET MVC? Part 2

In order to support categories of controllers in ASP.NET MVC, one must create a new controller factory.  The IControllerFactory interface defines two methods to implement, CreateController, which passes in the controller name pulled from the route data, as well as the RouteContext itself, and ReleaseController, which occurs at the end of the request, allowing the controller to be freed.  One limitation of CreateController method is that it only gives the controller name, but not the category name.  This isn't much of an issue, it's possible to obtain the category name by calling the GetRequiredString method from RequestContext.RouteData object (this is the same method used to obtain the controller and action name as well).  The question now, how does one obtain a reference to the correct Controller type?  A quick assumption as to how it is done without categories would be to reflect until the correct controller is found, then returning that.  Based on this assumption, one would assuming finding a matching Controller, then checking the attributes to determine that the corresponding attribute is set.

 

However, that's not how the default controller factory works, and for good reason.  It's very poor performance to reflect the entire set of types searching for the correct type.  Instead, the default controller factory creates what is known as a 'ControllerTypeCache' where it reflects through every type to find all matching controller types only once (when the application starts) and saves that list.  It uses a Dictionary<string, ILookup<string, Type>> to store this list.  A Dictionary is a one to one mapping between key and value, whereas a Lookup is a one to many mapping between key and value.  For each controller type that is found, it's name is derived, then that name is used to obtain the Lookup from the Dictionary (or create a new one if necessary).  Then, the namespace is taken from the controller type, and then used as a key to add the type to the Lookup.  So, each controller name is then mapped to one or more namespaces, which is then mapped to one or more types (more than one type with the same namespace and same name can occur if they are defined in separate assemblies).

 

A modified version of this same procedure can be used to support category lookup as well.  In this case, it might be preferable to store in an IDictionary<string, IDictionary<string, Type>>, where each category then maps to one or more controller names, which then map to one type.  The list can be easily generated using the following code:

private static IDictionary<string, IDictionary<string, Type>> GetAllControllerTypes() {
    // Go through all assemblies referenced by the application and search for
    // controllers.
    IDictionary<string, IDictionary<string, Type>> controllerTypes = new Dictionary<string, IDictionary<string, Type>>();
    ICollection assemblies = BuildManager.GetReferencedAssemblies();
    foreach (Assembly assembly in assemblies) {
        Type[] typesInAsm;
        try {
            typesInAsm = assembly.GetTypes();
        }
        catch (ReflectionTypeLoadException ex) {
            typesInAsm = ex.Types;
        }
        foreach (var controllerType in typesInAsm.Where(IsControllerType)){
            string category = GetCategoryName(controllerType);
            if (!string.IsNullOrEmpty(category)){
                if (!controllerTypes.ContainsKey(category)) controllerTypes.Add(category, new Dictionary<string, Type>());
                controllerTypes[category].Add(GetControllerName(controllerType), controllerType);
            }
        }
     }
    return controllerTypes;
}

Types can easily be looked up using the following code:  cache[categoryName][controllerName] (obviously, this is just a simple method demonstrating the structure; in order to prevent a NullReferenceException, you would want to use TryGetValue instead).

Print | posted on Monday, February 09, 2009 3:04 PM

Comments on this post

No comments posted yet.

Your comment:

 (will show your gravatar)
 
Please add 4 and 6 and type the answer here: