深入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的激活。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM