PBMining

Searching...
Monday, July 30, 2012

Exploring MVC framework in deep - ControllerTypeCache Class

10:16 AM

This is the class where the controller types is retrieved from cache using TypeCacheUtil class, grouping controller types by its name or retrieve a requested controller from given namespaces.

DefaultControllerFactory use this class to retrieved controller types. it fires AmbiguousControllerException if more than one same named controller is found in given namespaces that is bound with it.let's have a look on this class.

    internal sealed class ControllerTypeCache {
          private const string _typeCacheName = "MVC-ControllerTypeCache.xml";

          private Dictionary<string, ILookup<string, Type>> _cache;
          private object _lockObj = new object();
  }

There is one more method EnsureInitialized is called by DefaultControllerFactory. This method use TypeCacheUtil class and retrieve all classes that is type of Controller with parameter of type cache file name "MVC-ControllerTypeCache.xml" ,predicate for identify controller types and BuildManagerWrapper instance. After that controller type grouped by its names and make a dictionary that contain each controller types and namespaces on which this controller type is found. It could be more than one.

 public void EnsureInitialized(IBuildManager buildManager) {
              if (_cache == null) {
                  lock (_lockObj) {
                      if (_cache == null) {
                          List<Type> controllerTypes = TypeCacheUtil.GetFilteredTypesFromAssemblies(_typeCacheName, IsControllerType, 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);
                      }
                  }       }      }

Next method GetControllerTypes called by DefaultControllerFactory and it passes controller name and namespace list in which controller should exists. DefaultControllerFactory get this namespace list from the parameter specified in routes and in default namespaces values set in ControllerBuilder.

        public ICollection<Type> GetControllerTypes(string controllerName, HashSet<string> namespaces) {
                     HashSet<Type> matchingTypes = new HashSet<Type>();

                     ILookup<string, Type> nsLookup;
                     if (_cache.TryGetValue(controllerName, out nsLookup)) {
                         // this friendly name was located in the cache, now cycle through namespaces
                         if (namespaces != null) {
                             foreach (string requestedNamespace in namespaces) {
                                 foreach (var targetNamespaceGrouping in nsLookup) {
                                     if (IsNamespaceMatch(requestedNamespace, targetNamespaceGrouping.Key)) {
                                         matchingTypes.UnionWith(targetNamespaceGrouping);
                                     }
                                 }    }
                         }
                         else {
                             // if the namespaces parameter is null, search *every* namespace
                             foreach (var nsGroup in nsLookup) {
                                 matchingTypes.UnionWith(nsGroup);
                             }
                         }      }
                     return matchingTypes;
                 }
    

GetControllerTypes method find controller with given name in the cache with its namespaces. If controller found in cache then requested namespaces and targetnamespaces will match using IsNamespaceMatch internal method that match namespace or its sub namespaces.


     internal int Count {
              get {
                  int count = 0;
                  foreach (var lookup in _cache.Values) {
                      foreach (var grouping in lookup) {
                          count += grouping.Count();
                      }
                  }
                  return count;
              }   }


          internal static bool IsControllerType(Type t) {
              return  t != null &&  t.IsPublic && t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
                  !t.IsAbstract && typeof(IController).IsAssignableFrom(t);
          }


          internal static bool IsNamespaceMatch(string requestedNamespace, string targetNamespace) {
              // degenerate cases
              if (requestedNamespace == null) {     return false;
        }   else if (requestedNamespace.Length == 0)       {         return true;        }

              if (!requestedNamespace.EndsWith(".*", StringComparison.OrdinalIgnoreCase)) {
                  // looking for exact namespace match
                  return String.Equals(requestedNamespace, targetNamespace, StringComparison.OrdinalIgnoreCase);
              }
              else {
                  // looking for exact or sub-namespace match
                  requestedNamespace = requestedNamespace.Substring(0, requestedNamespace.Length - ".*".Length);
                  if (!targetNamespace.StartsWith(requestedNamespace, StringComparison.OrdinalIgnoreCase)) {
                      return false;
                  }

                  if (requestedNamespace.Length == targetNamespace.Length) {
                      // exact match
                      return true;
                  }
                  else if (targetNamespace[requestedNamespace.Length] == '.') {
                      // good prefix match, e.g. requestedNamespace = "Foo.Bar" and targetNamespace = "Foo.Bar.Baz"
                      return true;
                  }
                  else {
                      // bad prefix match, e.g. requestedNamespace = "Foo.Bar" and targetNamespace = "Foo.Bar2"
                      return false;
                  }
              }       }     }
    

All matched namespaces controller type will stored in hashset and return it to DefaultControllerFactory. If namespaces parameter of this method is null then every namespaces for the given controller is searched and return. In this class there is one more internal method IsControllerType it is used to identify valid controller type by applying some criteria. Count property returns count of namespaces groups available in cache.