前言:
mvc在beta版時就開始接觸了,通過博客園里很多人的分享很學到很多,在這里非常感謝他們,mvc很靈活擴展點很多。但如果沒有深入了解其源碼實現過程,只通一些擴展點文章了解如何擴展,會存在盲區,就是不知道為什么可以這樣做。想要加深了解,讀熟源碼是非常重要的,只有通過其源碼了解來龍去脈,才能方便的用自己的方式去擴展,以下是我以前的一次讀mvc源碼過程記錄,很亂,這回算是整理並回顧下。
此文適合己了解asp.net mvc基本流程,想加深認識asp.net mvc的同志,是基於mvc 2.0 的,比較早了,但我覺得很多東東在現在還是差不多的,可供學習參考。如果有講的不對的地歡迎給我指正。
初用mvc的朋友是否對添加路由規則有點迷茫,他具體是怎么來映射到控制器的。又是怎么來生成url的,要怎么合理的添加路由規則。
mvc默認給了我們很多約定,比如:你的視圖文件必須放在 Views/xxx/ 文件夾里面,建一個 Area ,默認給建一個 Areas/xxx/..目錄。
然后 AreaRegistration.cs 成為該area的路由注冊,這里注冊路由是怎么被 Global.asax 里的 AreaRegistration.RegisterAllAreas() 執行的。
是否可以把所有area路由集中放到一個地方按你定制的代碼注冊。是否可以把 Controller 放到別的地方去,是否想把所有 area 里面的 view 拿出來放一個專門存放 view 的項目中去?...此處省略109.5個字。
在框架各種約定下,有時會覺得不爽,不能自己隨意組識自己的文件,但如果你細讀源碼,就發會現,其實所有的一切,都可以自己定制的。
必備 Reflector 等源碼查看工具。
Routing - 路由注冊(嚴格說Routing不只屬於mvc,但我這里當作是mvc源碼一部分來講了)
mvc所有的請求都是通過路由規則去映射的,所以mvc的頭等大事就是路由規則的注冊,也是asp.net mvc第一件要做的事,因為規則確定了,才能知道當前請求應該映射到哪個Controller的哪個Action。
規則的注冊是在 Global.asax 的 Application_Start 事件里注冊,以下是默認的路由注冊代碼:

{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute( " {resource}.axd/{*pathInfo} ");
routes.MapRoute(
" Default ", // Route name
" {controller}/{action}/{id} ", // URL with parameters
new { controller = " Home ", action = " Index ", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
}
默認在Application_Start里調用了兩個方法。AreaRegistration.RegisterAllAreas() 和 RegisterRoutes(RouteTable.Routes)
前者是注冊所有的Area路由規則,area規則通常在添加area后自動產生,如Areas/xxx/xxxAreaRegistration.cs,后者是調用了當前的靜態方法,注冊了當前非area的路由,可以通過源碼看下RegisterRoutes(RouteTable.Routes),了解mvc路由的注冊。
這個方法傳入了 RouteTable.Routes 這個屬性,我們可以通過 Reflector 去查看 RouteTable 類。在 System.Web.Routing.dll 里面,

{
// Fields
private static RouteCollection _instance;
// Methods
static RouteTable();
public RouteTable();
// Properties
public static RouteCollection Routes { get; }
}
靜態構造及Routes屬性。
{
_instance = new RouteCollection();
}
public static RouteCollection Routes
{
get
{
return _instance;
}
}
通過上面的源碼可以看到, RouteTable ,只有一個職責,就是構建一個單例靜態 RouteCollection 類,這個類是用來保存路由規則(Route)的集合。RouteTable.Routes 屬性就是指向這個靜態 RouteCollection 實例,上面就是傳入了這個實例,並調用其MapRoute方法添加路由規則。

{
routes.IgnoreRoute( " {resource}.axd/{*pathInfo} ");
routes.MapRoute(
" Default ", // Route name
" {controller}/{action}/{id} ", // URL with parameters
new { controller = " Home ", action = " Index ", id = UrlParameter.Optional } // Parameter defaults
);
}
我們再查看 RouteCollection.MapRoute(... 添加規則。當看到這個類的源碼時,並沒有發現有 MapRoute 這些注冊方法。
通過vs里跟綜可以發現他是在 System.Web.Mvc.RouteCollectionExtensions 這個類的擴展方法里的。可能是 Routing 現在作為一個單獨的組件,考濾到可以在不同的需求里擴展不同的注冊規則方式。

{
// Methods
private static RouteCollection FilterRouteCollectionByArea(RouteCollection routes, string areaName, out bool usingAreas);
public static VirtualPathData GetVirtualPathForArea( this RouteCollection routes, RequestContext requestContext, RouteValueDictionary values);
public static VirtualPathData GetVirtualPathForArea( this RouteCollection routes, RequestContext requestContext, string name, RouteValueDictionary
values);
internal static VirtualPathData GetVirtualPathForArea( this RouteCollection routes, RequestContext requestContext, string name, RouteValueDictionary
values, out bool usingAreas);
public static void IgnoreRoute( this RouteCollection routes, string url);
public static void IgnoreRoute( this RouteCollection routes, string url, object constraints);
public static Route MapRoute( this RouteCollection routes, string name, string url);
public static Route MapRoute( this RouteCollection routes, string name, string url, object defaults);
public static Route MapRoute( this RouteCollection routes, string name, string url, string[] namespaces);
public static Route MapRoute( this RouteCollection routes, string name, string url, object defaults, object constraints);
public static Route MapRoute( this RouteCollection routes, string name, string url, object defaults, string[] namespaces);
public static Route MapRoute( this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces);
// Nested Types
private sealed class IgnoreRouteInternal : Route
{
// Methods
public IgnoreRouteInternal( string url);
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary routeValues);
}
}
上面可以看到有IgnoreRoute 方法,默認的第一條路由就是通過這個方法注冊,如: routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 這個。
這也是一個注冊路由方法,這樣的規則是用來干嘛?這里先不作分析,等下幾篇會詳細再講。可以看到上面己經有MapRoute
這個注冊方法了,都是一些重載,我們點開最后一個看具體實現。

{
if (routes == null)
{
throw new ArgumentNullException( " routes ");
}
if (url == null)
{
throw new ArgumentNullException( " url ");
}
Route route2 = new Route(url, new MvcRouteHandler()) {
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
};
Route item = route2;
if ((namespaces != null) && (namespaces.Length > 0))
{
item.DataTokens[ " Namespaces "] = namespaces;
}
routes.Add(name, item);
return item;
}
上面的代創建了一個 Route ,Route就是一條具體的路由,下次再單獨詳細Route分析這個類,因為Route是路由映射中一個很重要的成員。這里我們只要知道Route就是具體的一條路由規則,通過包裝傳入的參數后被添加到了RouteTable.Routes(全局路由集合),這是在整個網站的生命周期里一直隨時可以訪問路由集合。為后面的請求或是生成url起着重要的作用。這些后續會去分析。
我們可以在Application_BeginRequest打印所有己注冊的規則url,同時以后我們可以這樣去測試url的有效性及匹配情況。這些后續會提到。
{
RouteCollection routes = RouteTable.Routes;
foreach (Route route in routes)
{
Response.Write(route.Url + " <br /> ");
}
}
上面就是默認非area的路由注冊,我們再看下area的注冊又是如何進行的。
自從asp.net mvc2.0 就引入了area,可以讓我們把不同的模塊分格開來,我們默認添加一個 area,自動在 Areas 文件下創建。然后又為每個area添加了一個注冊規則的文件,如:Areas/Admin/AdminAreaRegistration.cs
那么這里面的規則又是如何被注冊到的呢,這里的又和前面的有什么區別,可以把area的路由注冊放到 Application_Start 里去注冊嗎?
我們在Application_Start里看到先是調用了AreaRegistration.RegisterAllAreas(),這個就是先注冊所有area的路由。我們去看下這個方法。
AreaRegistration 這個類是在 System.Web.Mvc.dll ,你可以通過下載mvc的源碼查看,我這里是用 Reflector 查看,這個類被定義為abstract。實際我們創建的Areas里的xxxAreaRegistration.cs,就是繼承這個類,並實現了RegisterArea(AreaRegistrationContext context) , AreaName { get; }
AreaRegistration 類

{
// Fields
private const string _typeCacheName = " MVC-AreaRegistrationTypeCache.xml ";
// Methods
protected AreaRegistration();
internal void CreateContextAndRegister(RouteCollection routes, object state);
private static bool IsAreaRegistrationType(Type type);
public static void RegisterAllAreas();
public static void RegisterAllAreas( object state);
internal static void RegisterAllAreas(RouteCollection routes, IBuildManager buildManager, object state);
public abstract void RegisterArea(AreaRegistrationContext context);
// Properties
public abstract string AreaName { get; }
}
首先看下他的靜態方法先后調用

{
RegisterAllAreas( null);
}
public static void RegisterAllAreas( object state)
{
RegisterAllAreas(RouteTable.Routes, new BuildManagerWrapper(), state);
}
internal static void RegisterAllAreas(RouteCollection routes, IBuildManager buildManager, object state)
{
foreach (Type type in TypeCacheUtil.GetFilteredTypesFromAssemblies( " MVC-AreaRegistrationTypeCache.xml ", new Predicate<Type>(AreaRegistration.IsAreaRegistrationType), buildManager))
{
((AreaRegistration) Activator.CreateInstance(type)).CreateContextAndRegister(routes, state);
}
}
可以看到最終是是通過TypeCacheUtil.GetFilteredTypesFromAssemblies過濾找所有MVC-AreaRegistrationTypeCache.xml這樣一個常量標識的類,而上面的AreaRegistration 里恰好有這個值為 MVC-AreaRegistrationTypeCache.xml 常量,可能是做為識別標識,這部分比較復雜,而且也沒太大必要去細做研究。我們只要大概猜到,這里就是取所有繼承了AreaRegistration的類(就是我們創建area后自帶的注冊路由的類)。並創建實例,然后調用 CreateContextAndRegister(routes, state); routes是前講到的全局路由集合,state,這里上面是傳入了null。我們再去看下AreaRegistration的這個CreateContextAndRegister方法。

{
AreaRegistrationContext context = new AreaRegistrationContext( this.AreaName, routes, state);
string str = base.GetType().Namespace;
if (str != null)
{
context.Namespaces.Add(str + " .* ");
}
this.RegisterArea(context);
}
首先是創建了一個 AreaRegistrationContext,這個類我們可以理解為一個打包。就是把 AreaName Routes state這些東東打包,以下構造方法。

{
this._namespaces = new HashSet< string>(StringComparer.OrdinalIgnoreCase);
if ( string.IsNullOrEmpty(areaName))
{
throw Error.ParameterCannotBeNullOrEmpty( " areaName ");
}
if (routes == null)
{
throw new ArgumentNullException( " routes ");
}
this.AreaName = areaName;
this.Routes = routes;
this.State = state;
}
然后如果非null就把空間命名也加入到AreaRegistrationContext的Namespaces屬性集合中。
最后把包裝好的數據傳入調用了 this.RegisterArea(context); 這就是我們 Areas/xxx/xxxAreaRegistration.cs 里實現了AreaRegistration的方法,完成了當前area的路由規則注冊。

{
public override string AreaName
{
get
{
return " Admin ";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
" Admin_default ",
" Admin/{controller}/{action}/{id} ",
new { action = " Index ", id = UrlParameter.Optional }
);
}
}
再看一下這里的RegisterArea里的注冊是通過 AreaRegistrationContext 類的 MapRoute 方法了,而不是上篇非area注時冊的RouteCollection擴展MapRoute方法進行注冊了,我們去看下 AreaRegistrationContext 這個類的MapRoute方法是怎樣的。

{
if ((namespaces == null) && ( this.Namespaces != null))
{
namespaces = this.Namespaces.ToArray< string>();
}
Route route = this .Routes.MapRoute(name, url, defaults, constraints, namespaces);
route.DataTokens[ " area "] = this.AreaName;
bool flag = (namespaces == null) || (namespaces.Length == 0);
route.DataTokens[ " UseNamespaceFallback "] = flag;
return route;
}
看標注的那行。this.Routes ,就是前打包 AreaRegistrationContext 的值,實際還是 RouteTable.Routes,也就是最后還是和非area一樣。只是后面多了些操作。就是往 route.DataTokens 屬性里,加了兩項值。這也是與非area規注注冊的一點區別。
bool flag = (namespaces == null) || (namespaces.Length == 0);
route.DataTokens[ " UseNamespaceFallback "] = flag;
這里我們基本可以確定,只要 Route 的 DataTokens 屬性里,有 area 這項值,就是一個area規則。
UseNamespaceFallback 這項值我以前的記錄里沒有,我記得以前整個mvc流程完了,好像也沒太看到這個屬性用在哪,這里先不管,不加也一樣。分析到再說吧。我們現在可以試一下,把area 里的 AdminAreaRegistration.cs 刪了。把area的路由規則放在Application_Start()里去注冊,如下:

{
routes.IgnoreRoute( " {resource}.axd/{*pathInfo} ");
// 這里是area
Route route = routes.MapRoute(
" Admin_default ",
" Admin/{controller}/{action}/{id} ",
new { controller = " Home ", action = " Index ", id = UrlParameter.Optional },
new string[] { " MVCTest.Areas.Admin.Controllers " }
);
route.DataTokens[ " area "] = " Admin ";
// 這里是area
routes.MapRoute(
" Default ", // Route name
" {controller}/{action}/{id} ", // URL with parameters
new { controller = " Home ", action = " Index ", id = UrlParameter.Optional }, // Parameter defaults
new string[] { " MVCTest.Controllers " }
);
}
protected void Application_Start()
{
// AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
然后訪問 /admin/home 我們可以發現可以正常請求area的。
這樣如果喜歡把規則放到一起注冊的朋友可以這樣了,不必再在每個 xxxAreaRegistration.cs 文件里注冊了。
或者用你自己的方式,反正簡單的說,注冊路由規則,就是往 RouteCollection 集合里添加 Route,而area的注冊,就是 Route的DataTokens["area"] = "Admin"; 指定其area名稱。而當 DataTokens 含有 area 值,mvc后面生成url和指定view都會用到這個值,后續我們會看到。
結尾語:
本文會跟着mvc的整個執行流程依次把多數源碼分析下去。其中結合了自己的一些見解,如果有什么不正確的地方還請指出。
轉載請注明出處,謝謝。