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对象。