利用少有的空余時間,詳細的瀏覽了下ASP.NET MVC 4的源代碼。照着之前的步伐繼續前進(雖然博客園已經存在很多大牛對MVC源碼分析的博客,但是從個人出發,還是希望自己能夠摸索出這些)。首先有一個事實我們需要明白,就是ASP.NET MVC是基於ASP.NET的,並不是獨立開來的,所以我們的伊始將會從路由配置入手。
在開始本節之前,需要讀者對ASP.NET的路由配置以及C#的擴展方法有一定的掌握,如果讀者不理解請根據情況選擇下面的文章進行充電:
RouteCollectionExtensions.cs
下面我們打開任意一個ASP.NET MVC項目(以下示例均為ASP.NET MVC 4,如果是其他版本請到響應的文件中查找),打開路由映射的文件(MVC4為App_Start下的RouteConfig.cs文件),可以看到我們再也熟悉不過的配置了,下面我們就來看看MapRoute背后的源碼是什么樣的

1 public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) 2 { 3 if (routes == null) 4 { 5 throw new ArgumentNullException("routes"); 6 } 7 if (url == null) 8 { 9 throw new ArgumentNullException("url"); 10 } 11 //這里的MvcRouteHandler就是一個重要的切入點 12 Route route = new Route(url, new MvcRouteHandler()) 13 { 14 Defaults = new RouteValueDictionary(defaults), 15 Constraints = new RouteValueDictionary(constraints), 16 DataTokens = new RouteValueDictionary() 17 }; 18 //而這里的DataTokens僅僅只是作為附加的參數 19 //作為后面搜索控制器時的一個條件 20 if ((namespaces != null) && (namespaces.Length > 0)) 21 { 22 route.DataTokens["Namespaces"] = namespaces; 23 } 24 routes.Add(name, route); 25 return route; 26 }
這個方法是對RouteCollection的擴展,其實就是RouteTable的Routes。我們可以看到源碼完全是利用ASP.NET的路由映射創建的,其中的關鍵點就是這個MvcRouteHandler,這是講所有符合的請求都經由MVC處理的入口。
MvcRouteHandler.cs
我們順藤摸瓜來到了MvcRouteHandler,這個類自然也要符合ASP.NET的要求,所以要實現IRouteHandler接口,其中有個關鍵的方法就是GetHttpHandler,這個方法要求返回一個實現了IHttpHandler接口的類型,而我們可以輕松的看到最終返回了一個MvcHandler類型
MvcHandler.cs
緊接着我們來到MvcHandler這個類,可以看到它不僅僅實現了IHttpHandler接口,同時還實現了IHttpAsyncHandler和IRequiresSessionState接口,前者是為了實現異步控制器,后者是為了訪問會話(Session)
然后我們查看接口是如何實現的
(IHttpHandler中ProcessRequest的實現)
(IHttpAsyncHandler中BeginProcessRequest的實現)
PS:這部分代碼比較長,所以沒有全部截取,並且重點也不在那。
通過兩個截圖讀者可以看到筆者紅色框住的部分,也能夠知道他們是我們接下來的主角,從它的參數也能夠看出這個方法將會返回給我們控制器的實現(IController)。下面我們長話短說,直接看這個方法中的關鍵部分
這里首先通過路由參數獲取了控制器的名稱,然后調用ControllerBuilder的GetControllerFactory方法獲取控制器工廠,我們可以簡單的看下GetControllerFactory的是如何實現的
ControllerBuilder.cs
呵呵,這當然我們要看的,下面我們會看到Current的具體類型
這里筆者就沒有繼續探索進去了,因為我們已經得出控制器工廠的具體類型是DefaultControllerFactory,那么我們回到MvcHandler中,可以看到在獲取了控制器工廠之后,就調用了它的CreateController方法,所以我們就打開DefaultControllerFactory.cs文件一探究竟。
DefaultControllerFactory.cs
二話不說,我們直接Search關鍵字CreateController就看到了下面這段代碼
OK,我們可以看到在CreateController中首先調用了GetControllerType獲取控制器的類型,然后再通過GetControllerInstance將控制器創建出來,既然本文的目的是探索控制器是如何定位查找的,所以我們就從GetControllerType入手,接招看代碼 J
上圖僅僅只是默認命名空間的情況,如果讀者指定了查找的命名空間則是另外實現的代碼,但是其中都是通過調用GetControllerTypeWithinNamespaces來查找的,所以我們就不必在此就留,直接F12來到這個方法中
到了這里,我們不能盲目自信,看到GetControllerTypes就興沖沖的F12進去,因為在這個方法之前的EnsureInitialized才是真正的關鍵部分,所以我們要跟進去。
ControllerTypeCache.cs
下面是EnsureInitialized的具體代碼
其中我們看到它首先是判斷_cache是否為NULL,如果不為NULL是不會進行下面的操作的,這就是為什么第一次訪問頁面的時候會很慢,而之后就很快了,原因就在這了。當然我們的重點可不是討論緩存的,我們看到TypeCacheUtil的GetFilteredTypesFromAssemblies返回的了一組類型,而這些就是所有的控制器,所以我們繼續追下去。
TypeCacheUtil.cs
以下就是GetFilteredTypesFromAssemblies的代碼
我們首先不考慮緩存,認為當前沒有緩存。那么我們就可以發現其中通過調用FilterTypesInAssemblies獲取到一組類型,然后才通過SaveTypesToCache保存至緩存中。既然我們先查看FilterTypesInAssemblies的實現代碼
到這里我們就快看到湖底了,因為我們看到了Assembly,而這段代碼則是通過遍歷所有的程序集將所有的類添加進typesSoFar中,然后在return部分通過TypeIsPublicClass過濾一遍,這個時候只會剩下公開的,非純虛的類了,而predicate則是在ControllerTypeCache中調用GetFilteredTypesFromAssemblies方法時傳入的委托,可以見如下所示的圖
而這個方法的具體代碼如下
呵呵就是判斷這個類型是否是Controller結尾,並且是否實現了Icontroller接口,這樣我們就獲取到了所有的控制器了。
細心的讀者會發現FilterTypesInAssemblies方法中buildManager.GetReferencedAssemblies();到底是調用的什么類型呢?以及什么方法呢?下面我發個圖,大家就能夠明白了
小結
到此為止我們基本上就探索完了,如何按照路由參數的名稱查找到具體的控制器這個任務就留給讀者了,因為已經很明了,本身查找出來的控制器類型就是按照名字、類型保存進字典的,獲取就非常簡單了。