mvc源码解析(6)-获取ControllerType

   为什么要讲ControllerType?其实刚开始的时候并没有讲这个ControllerType的打算,但是在看mvc源码的时候,心里老是有个疙瘩,一直在琢磨ControllerType的含义,随着研究的深入才慢慢发现它的真正含义。ControllerType是出现在MvcHandler创建控制器的时候用到的:

    controller = factory.CreateController(RequestContext, controllerName);

factory就是默认的DefaultControllerFactory的对象,我们可来看看CreateController的定义:

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

在创建controller之前,会根据当前的请求上下文和控制器的名称来获取controllerType,controllerType里面包含命名空间在内的Controller的具体信息。获取的ControllerType最终返回的是GetControllerTypeWithinNamespaces方法返回的值,具体实现如下:

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

     }        

}

 看到红色的代码我们可以猜到是在由ControllerTypeCache建立的缓存中直接将匹配的matchingTypes取出来,我们直接看里面的方法实现吧:

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;        

}

 在这句代码中我也要注意一点:我们请求的controller所在的命名空间存储HashSet<String>中,而我们每次请求一次Action的时候,会将当前Action所在的Controller的名称和Controller所在的命名空间缓存在 ILookup<string, Type>中。我们来看红色代码IsNamespaceMatch方法,判断请求的controller所在的命名空间与目标命名空间是否一致,具体的方法实现如下:

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;                

                            }            

                    }        

              }

判断的具体逻辑我们不做详解,我们回到这么一句:

 ICollection<Type> matchingTypes = ControllerTypeCache.GetControllerTypes(controllerName, namespaces);

 获取到ControllerType之后,判断集合matchingTypes中找到的ControllerType的数量,如有一个则直接返回,如有多个匹配的项,则抛出异常。很显然,在同一个命名空间下不能有相同名称的Controller。这样我们就获取到了具体的ControllerType对象。

posted @ 2013-02-16 10:40  肖&申&克  阅读(572)  评论(0编辑  收藏  举报