Click here to Skip to main content
15,881,882 members
Articles / Web Development / ASP.NET

DefaultControllerFactory in ASP.NET MVC

Rate me:
Please Sign up or sign in to vote.
4.00/5 (5 votes)
28 May 2013CPOL9 min read 40.6K   21   2
This article gives a complete description of the process that is followed by the DefaultControllerFactory in order to create an instance of the controller class that needed inorder to generate the response.

Whenever a request is received by MVC, it is the job of the routing engine to match the request URL with the registered routes. After finding the matched route, the MvcRouteHandler is called to provide a suitable handler for the request. It is the MvcHandler which is tasked with the generation of response for the ongoing request being processed. In its ProcessRequestInit() method, a call is made to to get the controller factory, which returns an instance of the DefaultControllerFactory class. Using this instance of the DefaultControllerFactory class, the CreateController() method is invoked to return a controller. You can know more about MvcRouteHandler and MvcHandler in my previous post. 

In this post, let's explore the process of controller instantiation by DefaultControllerFactory class.

The DefaultControllerFactory class implements IControllerFactory interface. It is the responsibility of the controller factory for the creation of an instance of controller class that needs to be invoked inorder to generate the response.

IControllerFactory Interface

The IControllerFactory interface exposes methods which when implemented by an any class makes the class to behave as a controller factory. We can create our custom controller factory by implementing IControllerFactory and registering it in the Application_Start() event by calling the SetControllerFactory() method. This is how IControllerFactory looks like : 

C#
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.

namespace System.Web.Mvc {
    using System.Web.Routing;
    using System.Web.SessionState;

    public interface IControllerFactory {
        IController CreateController(RequestContext requestContext, string controllerName);
        SessionStateBehavior GetControllerSessionBehavior(
                RequestContext requestContext, string controllerName);
        void ReleaseController(IController controller);
    }
}

It is the CreateController() method, to which the implementing class gives a body in order to create an instance of the matched controller. The ReleaseController() method is used for the disposal of the controller instance.

DefaultControllerFactory Class

The DefaultControllerFactory implements the methods of IControllerFactory as virtual. As default, MVC registers the DefaultControllerFactory as the factory for creating controllers in the ControllerBuilder class constructor.

DefaultControllerFactory() constructor 

In DefaultControllerFactory class, apart from the default constructor, it has a parametrized constructor accepting the IControllerActivator type. This allows one to create a custom controller activator with DefaultControllerFactory class.

Note : Entire code for this class can be found in codeplex.  

C#
// Copyright (c) Microsoft Open Technologies, Inc.
// All rights reserved. See License.txt in the project root for license information.

public DefaultControllerFactory()
    : this(null, null, null) {
}

public DefaultControllerFactory(IControllerActivator controllerActivator)
    : this(controllerActivator, null, null) {
}

internal DefaultControllerFactory(IControllerActivator controllerActivator, 
         IResolver<IControllerActivator> activatorResolver, 
         IDependencyResolver dependencyResolver) {
    if (controllerActivator != null) {
        _controllerActivator = controllerActivator;
    }
    else {
        _activatorResolver = activatorResolver ?? 
                new SingleServiceResolver<IControllerActivator>(
            () => null,
            new DefaultControllerActivator(dependencyResolver),
            "DefaultControllerFactory contstructor"
        );
    }
}

Both the default and parametrized constructor call another internal constructor. It is the responsibility of this internal controller to create an instance of the controller activator in case if it is not received by it. Now, if a controller activator is being received by it, that means that there is a custom implementation for controller activator that is being provided by the user, if a null value is received, it checks whether there is any service that resolves controller activator using the activatorResolver argument. The activatorResolver is of type IResolver<IControllerActivator>, it specifies a resolver that knows how to locate a IControllerActivator type. If both controllerActivator and activatorResolver values are null, then an instance of the DefaultControllerActivator is created. DefaultControllerActivator is an inner class inside the DefaultControllerFactory class. This is how the inner class looks :

C#
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.

private class DefaultControllerActivator : IControllerActivator {
    Func<IDependencyResolver> _resolverThunk;

    public DefaultControllerActivator()
        : this(null) {
    }

    public DefaultControllerActivator(IDependencyResolver resolver) {
        if (resolver == null) {
            _resolverThunk = () => DependencyResolver.Current;
        }
        else {
            _resolverThunk = () => resolver;
        }
    }

    public IController Create(RequestContext requestContext, Type controllerType) {
        try {
            return (IController)(_resolverThunk().GetService(controllerType) ?? 
                       Activator.CreateInstance(controllerType));
        }
        catch (Exception ex) {
            throw new InvalidOperationException(
                String.Format(
                    CultureInfo.CurrentCulture,
                    MvcResources.DefaultControllerFactory_ErrorCreatingController,
                    controllerType),
                ex);
        }
    }
}

In order to create an instance of DefaultControllerActivator class, a call is made to the SingleServiceResolver() method, which delegates the call to GetSingleService() method in DependencyResolver class, which creates an instance of the IControllerActivator type using its default constructor.

CreateController() method

This is how DefaultControllerFactory class gives body to the implemented CreateController() method along with its required internal methods.

C#
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.

private IControllerActivator ControllerActivator {
    get {
        if (_controllerActivator != null) {
            return _controllerActivator;
        }
        _controllerActivator = _activatorResolver.Current;
        return _controllerActivator;
    }
}

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");
    }
    Type controllerType = GetControllerType(requestContext, controllerName);
    IController controller = GetControllerInstance(requestContext, controllerType);
    return controller;
}

protected internal virtual IController GetControllerInstance(
          RequestContext requestContext, Type controllerType) {
    if (controllerType == null) {
        throw new HttpException(404,
            String.Format(
                CultureInfo.CurrentCulture,
                MvcResources.DefaultControllerFactory_NoControllerFound,
                requestContext.HttpContext.Request.Path));
    }
    if (!typeof(IController).IsAssignableFrom(controllerType)) {
        throw new ArgumentException(
            String.Format(
                CultureInfo.CurrentCulture,
                MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase,
                controllerType),
            "controllerType");
    }
    return ControllerActivator.Create(requestContext, controllerType);
}

protected internal virtual Type GetControllerType(
          RequestContext requestContext, 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;
    if (requestContext != null && 
              requestContext.RouteData.DataTokens.TryGetValue(
              "Namespaces", out routeNamespacesObj)) {
        IEnumerable<string> routeNamespaces = routeNamespacesObj as IEnumerable<string>;
        if (routeNamespaces != null && routeNamespaces.Any()) {
            HashSet<string> nsHash = 
              new HashSet<string>(routeNamespaces, StringComparer.OrdinalIgnoreCase);
            match = GetControllerTypeWithinNamespaces(
                       requestContext.RouteData.Route, controllerName, nsHash);

            // the UseNamespaceFallback key might not exist,
            // in which case its value is implicitly "true"
            if (match != null || false.Equals(
                    requestContext.RouteData.DataTokens["UseNamespaceFallback"])) {
                // got a match or the route requested we stop looking
                return match;
            }
        }
    }

    // then search in the application's default namespace collection
    if (ControllerBuilder.DefaultNamespaces.Count > 0) {
        HashSet<string> nsDefaults = new HashSet<string>(
              ControllerBuilder.DefaultNamespaces, StringComparer.OrdinalIgnoreCase);
        match = GetControllerTypeWithinNamespaces(
                     requestContext.RouteData.Route, controllerName, nsDefaults);
        if (match != null) {
            return match;
        }
    }

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

It is inside the MvcHandler class, ProcessRequestInit() method where an instance of DefaultControllerFactory class is created. After creation of the instance a call is made to the CreateController() method. The responsibilities of CreateController() method are as such

  • Searches for controller types that matches the controller name.
  • If it finds a single controller type that matches the name, instantiates that type and returns its instance.
  • If it finds more than one controller type matches the name then it throws an ambiguous exception.
  • If not even a single controller type is found, then it gives a http 404 response.

The above work to be done by CreateController() method is split into two virtual methods namely GetControllerType() and GetControllerInstance() methods.

Taking a closer look at the GetControllerType() method, we can conclude that, the first place the factory looks for the controller type is in the namespaces assigned in the RouteData<code><code><code><code><code><code><code><code><code>'s <code><code><code><code><code><code><code><code><code><code>DataTokens property. When we create a route we can pass the namespaces of the controllers that will handle the requests. Such as,

C#
routes.MapRoute(
        "Default",
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional },
        new[] { "MyMvcApp.Controllers" }
);

In order to check for the presence and get the namespaces the TryGetValue() method of DataTokens is called. A hashset is created out of the result of TryGetValue() method, inorder to get unique namespaces. Using the matched route for the controller, hashset and controller name a call is made to GetControllerTypeWithinNamespaces() method. This is how it looks :  

C#
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.

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

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

        case 1:
            // single matching type
            return matchingTypes.First();

        default:
            // multiple matching types
            throw CreateAmbiguousControllerException(
                         route, controllerName, matchingTypes);
    }
}

DefaultControllerFactory class uses reflection to discover the controller types in the assemblies. Reflection itself is a costly affair. So, inorder to avoid searching controllers every-time, the factory caches the discovered controller types. The ControllerTypeCache class is used for this purpose. All found controller types are stored as an xml file named as MVC-ControllerTypeCache.xml. The first thing the GetControllerTypeWithinNamespaces() method does is, it makes a call to the EnsureInitialized() method of ControllerTypeCache class. The result is, all the stored controller types are read from the xml file and are stored in a dictionary. After that a call is made to the GetControllerTypes() method of ControllerTypeCache class, passing the controller name and hashset of namespaces. The output is all the controller types that matches the name in the hashset of namespaces.

Depending upon the count of the matchingTypes, the return type is decided. If there no matching controller type for the hashset of namespaces send, then it returns a null value. If there is a single entry, then it returns the matching controller type. Otherwise if there is more than one, matching controller type, then a call is made to CreateAmbiguousControllerException() method which raises an InvalidOperationException<code> exception. This is how the CreateAmbiguousControllerException() method looks like :

C#
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.

internal static InvalidOperationException CreateAmbiguousControllerException(
         RouteBase route, string controllerName, ICollection<Type> matchingTypes) {
    // we need to generate an exception containing all the controller types
    StringBuilder typeList = new StringBuilder();
    foreach (Type matchedType in matchingTypes) {
        typeList.AppendLine();
        typeList.Append(matchedType.FullName);
    }

    string errorText;
    Route castRoute = route as Route;
    if (castRoute != null) {
        errorText = String.Format(CultureInfo.CurrentCulture, 
            MvcResources.DefaultControllerFactory_ControllerNameAmbiguous_WithRouteUrl,
            controllerName, castRoute.Url, typeList);
    }
    else {
        errorText = String.Format(CultureInfo.CurrentCulture, 
            MvcResources.DefaultControllerFactory_ControllerNameAmbiguous_WithoutRouteUrl,
            controllerName, typeList);
    }

    return new InvalidOperationException(errorText);
}

After getting the matched controller type, the control is back to the GetControllerType() method of DefaultConrollerFactory class.

Now the question is, what if there is no namespaces assigned in the RouteData's DataToken property or if it is unable to find a single controller type that matches the namespace; then it moves to the second place, that is inside the namespaces that are assigned in the DefaultNamespaces property of ControllerBuilder. Like, we pass the namespaces of controllers in routes we can also set the namespaces of the controllers at a global level also through the ControllerBuilder class. For example,

C#
ControllerBuilder.Current.DefaultNamespaces.Add("MyMvcApp.Controllers");

Again for these defined namespaces, the same above process will be followed, that is creation of a hashset of namespaces, making a call to GetControllerTypeWithinNamespaces() method to determine a controller type and returning the matching controller type, if there is one.

What if, it again fails for the above case, that is, there is no namespaces assigned in the RouteData's DataToken property (or namespaces are there but it is unable to find a single controller type that matches the namespace assigned in the RouteData's DataToken property) and there is no namespaces that are assigned in the DefaultNamespaces property of ControllerBuilder at global level (or namespace are there but it is unable to find a single controller type that matches the namespace assigned in the DefaultNamespaces property of ControllerBuilder). In such a case, it searches in all the namespaces of the current executing assembly as well as the referenced assemblies. In order to do this it makes a call to the GetControllerTypeWithinNamespaces() method with the null value for namespace argument, who forwards this null value to ControllerTypeCache class, GetControllerTypes() method. A null value tells the GetControllerTypes() method to search in all current executing assembly as well as the referenced assemblies.

NOTE : Now suppose if we want to prevent searching for controllers in other namespaces, which happens by default if it is not found in the specified namespace. We can do so by setting DataToken property UseNamespaceFallback value to false. This will stop checking of controller type in other namespaces, and will return a null value for the controller type.

Finally, the control is back to the CreateController() method, after getting a matched controller type. Using this controller type a call is made to the GetControllerInstance() method to create an instance of the matched type. If the controller type returned is null, then an HttpException exception is raised with http status code of 404. Otherwise another check is made to ensure that the matched controller type is assignable to IController interface. If everything goes fine using the ControllerActivator property we get a reference to the DefaultControllerActivator class (which was created by DefaultControllerFactory class constructor). Using the DefaultControllerActivator reference a call is made to its Create() method, which creates an instance of the controller, using the controller type default constructor.

ReleaseController()

The ReleaseController() method implementation, in the DefaultControllerFactory class looks like this : 

C#
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.

public virtual void ReleaseController(IController controller) {
    IDisposable disposable = controller as IDisposable;
    if (disposable != null) {
        disposable.Dispose();
    }
}

The name itself gives a clear indication that the method is used to release the controller instance.
The ReleaseController() method is called in the ProcessRequest() method of MvcHandler class, after the call to Execute() method for the controller has been completed. It marks the end of the process of response generation, with the release of the controller. In order to release the controller the Dispose() method of the Controller class is called (as every controller in ASP.NET MVC derives from Controller class). Dispose() method calls the SuppressFinalize() method of GC class, for the object it is disposing. SuppressFinalize tells the garbage collector that the object was cleaned up properly and doesn't need to go onto the the finalizer queue. 

GetControllerSessionBehavior()

The third and last implementation of IControllerFactory interface is the GetControllerSessionBehavior() method. It is called inside the GetSessionStateBehavior() method of MvcRouteHandler class. The method body is as such : 

C#
// Copyright (c) Microsoft Open Technologies, Inc.
// All rights reserved. See License.txt in the project root for license information.

private static readonly ConcurrentDictionary<Type, SessionStateBehavior> 
  _sessionStateCache = new ConcurrentDictionary<Type, SessionStateBehavior>();

SessionStateBehavior IControllerFactory.GetControllerSessionBehavior(
                     RequestContext requestContext, string controllerName) {
    if (requestContext == null) {
        throw new ArgumentNullException("requestContext");
    }
    if (String.IsNullOrEmpty(controllerName)) {
        throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
    }

    Type controllerType = GetControllerType(requestContext, controllerName);
    return GetControllerSessionBehavior(requestContext, controllerType);
}

protected internal virtual SessionStateBehavior GetControllerSessionBehavior(
                   RequestContext requestContext, Type controllerType) {
    if (controllerType == null) {
        return SessionStateBehavior.Default;
    }

    return _sessionStateCache.GetOrAdd(
        controllerType,
        type =>
        {
            var attr = type.GetCustomAttributes(typeof(SessionStateAttribute), inherit: true)
                           .OfType<SessionStateAttribute>()
                           .FirstOrDefault();

            return (attr != null) ? attr.Behavior : SessionStateBehavior.Default;
        }
    );
}

This method is used to manage the session state behaviour for the controller. By default, Asp.Net MVC supports session state. Session is used to store data values across requests. Whether you store some data values with in the session or not Asp.Net MVC manages the session state for all the controllers in your application. In order to manage the session state for the controller type, it checks for the presence of, SessionStateAttribute on given controller type, if it finds one, then it returns the SessionStateBehavior enumeration value set in the SessionStateAttribute, or else it returns the default session state i.e., SessionStateBehavior.Default. The SessionStateBehavior.Default enumeration value specifies the usage of default ASP.NET behaviour, which is to determine the session state configuration from HttpContext.

The SessionStateBehavior for the controller type gets stored in a static object of ConcurrentDictionary class, which makes it accessible by multiple request threads. It acts like a cache of all matched controller types with their corresponding SessionStateBehavior value.

With this we are done with the complete explanation of purpose and working of the DefaultControllerFactory class.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Mindfire Solutions
India India
Software developer, working at Mindfire Solutions, having hands on experience in both Windows and web application using C#, ASP.NET, ASP.NET MVC.

Comments and Discussions

 
GeneralMy vote of 3 Pin
DotNetCodebased19-Nov-14 17:10
DotNetCodebased19-Nov-14 17:10 
I believe diagram could speak much louder here...
GeneralMy vote of 3 Pin
saboor awan17-Sep-13 10:04
saboor awan17-Sep-13 10:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.