PBMining

Searching...
Tuesday, August 7, 2012

Exploring MVC framework in deep - DefaultControllerFactory Class

10:03 AM

In this article i am going to explore the DefaultControllerFactory class.how DefaultControllerFactory controller lookup is works and cached into the disk using various classes like ControllerTypeCache,BuildManagerWrapper.

There are lots of classes used byDefaultControllerFactory for creating a controller instance.

  • ControllerTypeCache
  • DefaultControllerActivator
  • SingleServiceResolver
  • BuildManagerWrapper

lets see how things work inside DefaultControllerFactory. this class has three constructor two constructor are public and one is internal so not accessible outside.

    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 constructor" );
               }
           }

Second constructor allow you to pass CustomControllerActivator that is used to get the instance of controller. you can see here both constructor call third internal constructor and pass parameter values to it if specified.

Third constructor get the custom values from the parameter if values is null then it used MVC default classes to provide services. It first check that controlleractivator is specified if not then it check that is there any service is provides that resolve controlleractivator if both of this parameter is null then its use SingleServiceResolver class that is built in MVC framework for creating service for resolve controllerActivator instance.

There are three methods that is exposed by IControllerFactory for creating CustomControllerFactory but before that we need to know how this methods works.

  • CreateController
  • GetControllerSessionBehavior
  • ReleaseController

CreateController method use GetControllerType and GetControllerInstance internal methods to retrieve the instance of controller.

    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;
    }
    

GetControllerType method is use for getting controller type within namespaces. GetControllerTypeWithinNamespaces uses ControllerTypeCache class to retrieve the type found within given namespace. If multiple types found then it raise ambiguous exception using CreateAmbiguousControllerException method.

GetControllerType method get namespace collections from two places. One from route definition and and default namespaces define within ControllerBuilder.DefaultNamespaces property.

It also check for the value of UseNamespaceFallback to enable or disable searching for controllers in other namespaces. If no namespaces found in above two places it search all namespace of applications.

    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 */);
          }

GetControllerInstance method get the instance of controller from controlleractivator that is resolved by activatorresolver. Before retrieving instance of given controller type it check that type should not be null values if null then it throw 404 HttpException. It also check given type should assignable to IController interface. See below implementation of MVC version of this class.

    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);
         }

Next customizable method of this class is GetControllerSessionBehavior. This method divided in two parts one is implementation of interface method and second one is internal helper method. This method is used to enable or disable sessionstate behaviour for controller. First search for SessionStateAttribute applied on given controller type and add or get sessionstate behaviour.

Object of ConcurrentDictionary store sessionstate behaviour for all controller classes so it can be accessible by multiple threads.

    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;
                 });
         }

At the end of the request ControllerFactory dispose the controller instance and release all resources, ReleaseController do this job.

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

please share this post if you found anything helpful in this post. thanks....