• Controller的激活


    上文说到Routing Module将控制权交给了MvcHandler,因为MvcHandler实现了IHttpAsyncHandler接口,因此紧接着就会调用BeginProcessRequest方法,这个方法首先会进行一些Trust Level之类的安全检测,暂且不谈,然后会调用ProcessRequestInit方法(有删节):

    private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) {
                // Get the controller type
                string controllerName = RequestContext.RouteData.GetRequiredString("controller");
                // Instantiate the controller and call Execute
                factory = ControllerBuilder.GetControllerFactory();
                controller = factory.CreateController(RequestContext, controllerName);
                if (controller == null) {
                    throw new InvalidOperationException(
                        String.Format(
                            CultureInfo.CurrentCulture,
                            MvcResources.ControllerBuilder_FactoryReturnedNull,
                            factory.GetType(),
                            controllerName));
                }
            }

    首先会获得controller的名字,然后会实例化controller,这里采用了抽象工厂的模式,首先利用ControllerBuilder获得一个IControllerFactory的实例,ControllerBuilder采用Dependency Injection来实例化IControllerFactory,关于MVC中DI的实现以后另文介绍,在默认情况下,ControllerBuilder会返回一个实例。接着调用CreateController方法:

    public virtual IController CreateController(RequestContext requestContext, string controllerName) {
    Type controllerType = GetControllerType(requestContext, controllerName);
                IController controller = DefaultControllerFactoryGetControllerInstance(requestContext, controllerType);
                return controller;
            }

    方法分为两步,先获得类型,再获得实例:

    protected internal virtual Type GetControllerType(RequestContext requestContext, string 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 */);
            }

    DefaultControllerFactory在根据路由信息查找对应Controller的类型的时候,首先判断DataToken中有没有Namespace,然后调用GetControllerTypeWithinNamespaces 方法查找Controller对应的类。先看下这个方法:

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

    ASP.NET中大量的用到了反射,因此也需要把这些反射出的类进行缓存以提高性能,首先看下EnsureInitialized这个比较有意思的方法,这个方法的参数BuildManager经过了层层包装,其实只是System.Web.Compilation.BuildManager的一个实例。

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

    首先TypeCacheUtil获得所有是Controller的类型。TypeCacheUtil在前文已经出现过,用来获取所有的AreaRegistration的子类型,这里仔细看下这个方法:

    public static List<Type> GetFilteredTypesFromAssemblies(string cacheName, Predicate<Type> predicate, IBuildManager buildManager) {
                TypeCacheSerializer serializer = new TypeCacheSerializer();
    
                // first, try reading from the cache on disk
                List<Type> matchingTypes = ReadTypesFromCache(cacheName, predicate, buildManager, serializer);
                if (matchingTypes != null) {
                    return matchingTypes;
                }
                // if reading from the cache failed, enumerate over every assembly looking for a matching type
                matchingTypes = FilterTypesInAssemblies(buildManager, predicate).ToList();
                // finally, save the cache back to disk
                SaveTypesToCache(cacheName, matchingTypes, buildManager, serializer);
                return matchingTypes;
            }

    这个方法会从缓存中读取controller类型的名字,缓存是存在一个文本文件中的,名字就是cacheName,在这里是Mvc-ControllerTypeCache.xml.这个文件的内容是如下样子的:

    <?xml version="1.0" encoding="utf-8"?> 
    <!--This file is automatically generated. Please do not modify the contents of this file.--> 
    <typeCache lastModified="11/4/2012 8:52:26 PM" mvcVersionId="a5d58bd9-3a4a-4d1d-a7ce-9cef11e4c380"> 
      <assembly name="MVCApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> 
        <module versionId="c7b3d847-7853-44f3-87d0-9cc040c4cb53"> 
          <type>MVCApp.Areas.Admin.Controllers.HomeController</type> 
          <type>MVCApp.Controllers.HomeController</type> 
        </module> 
      </assembly> 
    </typeCache>

    再看下参数predicate,这个参数是用来筛选哪些类是Controller,这个方法的实现也比较有意思:

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

    这就说明一个类如果要能够成为Controller, 必须以Controller结尾,必须是public的,必须实现IController接口。找到所有的Controller类之后,再回到EnsureInitialized方法中,为了方便查找,会对这些类进行索引,首先按照Controller的名字进行分组,然后再按照Namespace分组。下面很快可以看到,这样分组之后可以很方便的找到需要的Controller。

    回到 GetControllerTypeWithinNamespaces 方法,这时候缓存中已经有索引好的Controller的信息了,接着就是在缓存中根据Controller的名字查找Controller,GetControllerTypes实现了这个过程,过程并不复杂,但是细节不少,具体代码不贴出,过程是:首先查出controller名字对应的Lookup,再检查namespace是否符合。如果GetControllerTypeWithinNamespaces中的参数namespace为空或者没有内容,那么就只判断controller的名字。查找的结果有三种,只有一个Controller的type满足,那么就返回这个类型,如果没有找到则返回null,如果找到了多个就会抛出异常。

    回到GetControllerType方法,如果GetControllerTypeWithinNamespaces 返回了null,并且UseNamespaceFallback 设为true,那么会进行下一步的搜索,否则返回null。下一步的搜索就是在项目的DefaultNamespace下进行搜索,对于没有Namespace的RouteData,默认就是在这里搜索的。最后在所有的namespace中搜索。至此,根据controller名字查找controller类的type完成了。

    接下来就要实例化这个类型。实例化的方法简单些只需要调用

    Activator.CreateInstance(controllerType);

    就可以了。但是ASP.NET MVC在这里使用了较为复杂的DI机制,在默认情况下,它调用的是DefaultDependencyResolver的GetService方法,这个方法最终也仅仅是调用了Activator.CreateInstance方法。关于MVC中的DI机制,这里不多做分析,另文叙述。 至此,一个Controller类已经被构造出来了。下文介绍Action的激活。

  • 相关阅读:
    如何利用azMan (Authorization Manager) 实现 rolebased的安全验证机制
    如何处理源dump文件的mscordacwks.dll文件与调试机上的版本不一致问题而无法使用extension cmd的问题
    UI thread client callback和UI thread WCF Service一起工作时死锁的形成原因及解决方法
    如何在 IIS 6.0 上配置托管的 Web 应用程序时使用 SPN(包括Network service ,domain acount, NLB, host header等各种情况)
    WCF中各种Transaction的分类及其相关参数的设置
    利用windbg调试class type,value type以及MethodTable等强化C#的基本概念
    如何自定义Attribute class并将其应用到相应的class
    谷歌浏览器F12调试页面学习
    上班族致富之道
    折半插入排序
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2756337.html
Copyright © 2020-2023  润新知