• 深入ASP.NET MVC之三: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的激活。

     

  • 相关阅读:
    【转+补充】在OpenCV for Android 2.4.5中使用SURF(nonfree module)
    Delphi StarOffice Framework Beta 1.0 发布
    Angular ngIf相关问题
    angularjs文档下载
    公众号微信支付开发
    公众号第三方平台开发 教程六 代公众号使用JS SDK说明
    公众号第三方平台开发 教程五 代公众号处理消息和事件
    公众号第三方平台开发 教程四 代公众号发起网页授权说明
    公众号第三方平台开发 教程三 微信公众号授权第三方平台
    公众号第三方平台开发 教程二 component_verify_ticket和accessToken的获取
  • 原文地址:https://www.cnblogs.com/yinzixin/p/2754484.html
Copyright © 2020-2023  润新知