Saturday, September 17, 2011

ASP.NET MVC Internals 2: Controller

The MvcHandler class processes an HTTP request. The routing framework calls its ProcessRequest() function in case of an incoming request matching an MVC route.
public class MvcHandler : IHttpHandler, IRequiresSessionState 
{
    [...]
    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);
        if (controller == null) {
            throw new InvalidOperationException(
                String.Format(
                    CultureInfo.CurrentUICulture,
                    MvcResources.ControllerBuilder_FactoryReturnedNull,
                    factory.GetType(),
                    controllerName));
        }
        try {
            controller.Execute(RequestContext);
        }
        finally {
            factory.ReleaseController(controller);
        }
    }
    [...]
}

Firstly it reads the controller name from the routing data and after that it creates a controller by the name read before. Finally it calls the Execute method of the controller to run the controller’s code.



The ControllerBuilder is a factory for creating the controller instance.

public class ControllerBuilder 
{
    [...]
    private Func<IControllerFactory> _factoryThunk;

    public ControllerBuilder() 
    {
        SetControllerFactory(new DefaultControllerFactory() {
            ControllerBuilder = this
        });
    }

    [...]
    public IControllerFactory GetControllerFactory() {
        IControllerFactory controllerFactoryInstance = _factoryThunk();
        return controllerFactoryInstance;
    }

    public void SetControllerFactory(IControllerFactory controllerFactory) 
    {
        if (controllerFactory == null) {
            throw new ArgumentNullException("controllerFactory");
        }

        _factoryThunk = () => controllerFactory;
    }
    [...]
}
The constructor of the class at line 8 calls the SetControllerFactory method with a DefaultControllerFactory instance. Thus the DefaultControllerFactory class will look up and call the controller with the name provided by the RouteData.
public class DefaultControllerFactory : IControllerFactory 
{
    private IBuildManager _buildManager;
    private ControllerBuilder _controllerBuilder;
    private ControllerTypeCache _instanceControllerTypeCache;
    private static ControllerTypeCache _staticControllerTypeCache = new ControllerTypeCache();

    [...]

    public virtual IController CreateController(RequestContext requestContext, string controllerName) 
    {
        [...]
        RequestContext = requestContext;
        Type controllerType = GetControllerType(controllerName);
        IController controller = GetControllerInstance(controllerType);
        return controller;
    }

    protected internal virtual IController GetControllerInstance(Type controllerType) 
    {
        [...]
        try {
            return (IController)Activator.CreateInstance(controllerType);
        }
        catch (Exception ex) {
            throw new InvalidOperationException(
                String.Format(
                    CultureInfo.CurrentUICulture,
                    MvcResources.DefaultControllerFactory_ErrorCreatingController,
                    controllerType),
                ex);
        }
    }

    protected internal virtual Type GetControllerType(string controllerName) 
    {
        [...]

        // first search in the current route's namespace collection
        object routeNamespacesObj;
        Type match;
        [...]

        // then search in the application's default namespace collection
        [...]

        // if all else fails, search every namespace
        return GetControllerTypeWithinNamespaces(controllerName, null /* namespaces */);
    }

    private Type GetControllerTypeWithinNamespaces(string controllerName, HashSet<string> namespaces) 
    {
        // Once the master list of controllers has been created we can quickly index into it
        ControllerTypeCache.EnsureInitialized(BuildManager);

        IList<Type> matchingTypes = ControllerTypeCache.GetControllerTypes(controllerName, namespaces);
        switch (matchingTypes.Count) {
            case 0:
                // no matching types
                return null;

            case 1:
                // single matching type
                return matchingTypes[0];

            default:
                // multiple matching types
                // we need to generate an exception containing all the controller types
                StringBuilder sb = new StringBuilder();
                foreach (Type matchedType in matchingTypes) {
                    sb.AppendLine();
                    sb.Append(matchedType.FullName);
                }
                throw new InvalidOperationException([...]);
        }
    }

}
As soon as the DefaultControllerFactory.CreateController function, it finds the type of the controller by its name and creates an instance after that. The framework uses the ControllerTypeCache class to find the controller types.
internal sealed class ControllerTypeCache 
{
    [...]

    public void EnsureInitialized(IBuildManager buildManager) 
    {
        if (_cache == null) {
            lock (_lockObj) {
                if (_cache == null) {
                    List<Type> controllerTypes = GetAllControllerTypes(buildManager);
                    [...]
                }
            }
        }
    }

    private static List<Type> GetAllControllerTypes(IBuildManager buildManager) 
    {
        // Go through all assemblies referenced by the application and search for
        // controllers and controller factories.
        List<Type> controllerTypes = new List<Type>();
        ICollection assemblies = buildManager.GetReferencedAssemblies();
        foreach (Assembly assembly in assemblies) {
            Type[] typesInAsm;
            try {
                typesInAsm = assembly.GetTypes();
            }
            catch (ReflectionTypeLoadException ex) {
                typesInAsm = ex.Types;
            }
            controllerTypes.AddRange(typesInAsm.Where(IsControllerType));
        }
        return controllerTypes;
    }

    [...]

    internal static bool IsControllerType(Type t) {
        return
            t != null &&
            t.IsPublic &&
            t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
            !t.IsAbstract &&
            typeof(IController).IsAssignableFrom(t);
    }
}
The ControllerTypeCache.EnsureInitialized (called by DefaultControllerFactory.GetControllerTypeWithinNamespaces) uses the BuildManager class (in MVC, wrapped by BuildManagerWrapper that implements the internal IBuildManager interface) to find the available assemblies. The ControllerTypeCache.GetAllControllerTypes returns the controller types referenced by the project. A class is a controller type if the class is
  • public
  • its name ends with “Controller”, casing is ignored
  • the type is not abstract
  • the type “implements” (can be assigned to) the IController interface.
Finally the DefaultControllerFactory.GetControllerInstance creates the instance for the Type using Activator.CreateInstance() method. Now we have the controller instance to work with.
The MvcHandler.ProcessRequest() calls the controller.Execute() to run the controller code.
ASP.NET MVC Finding Controller
Link: ASP.NET MVC – Finding the controller

No comments:

Post a Comment