asp.net mvc源碼分析 - 路由注冊


前言:

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 事件里注冊,以下是默認的路由注冊代碼:

View Code
     public  class MvcApplication : System.Web.HttpApplication
    {
         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 里面, 

View Code
public  class RouteTable
{
     //  Fields
     private  static RouteCollection _instance;

     //  Methods
     static RouteTable();
     public RouteTable();

     //  Properties
     public  static RouteCollection Routes {  get; }
}

靜態構造及Routes屬性。

static RouteTable()
{
    _instance = new RouteCollection();
}
public static RouteCollection Routes
{
    get
    {
        return _instance;
    }
}

通過上面的源碼可以看到, RouteTable ,只有一個職責,就是構建一個單例靜態 RouteCollection 類,這個類是用來保存路由規則(Route)的集合。RouteTable.Routes 屬性就是指向這個靜態 RouteCollection 實例,上面就是傳入了這個實例,並調用其MapRoute方法添加路由規則。

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

我們再查看 RouteCollection.MapRoute(... 添加規則。當看到這個類的源碼時,並沒有發現有 MapRoute 這些注冊方法。
通過vs里跟綜可以發現他是在 System.Web.Mvc.RouteCollectionExtensions 這個類的擴展方法里的。可能是 Routing 現在作為一個單獨的組件,考濾到可以在不同的需求里擴展不同的注冊規則方式。

View Code
public  static  class RouteCollectionExtensions
{
     //  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
這個注冊方法了,都是一些重載,我們點開最后一個看具體實現。

View Code
public  static Route MapRoute( this RouteCollection routes,  string name,  string url,  object defaults,  object constraints,  string[] namespaces)
{
     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的有效性及匹配情況。這些后續會提到。

         protected  void Application_BeginRequest()
        {
             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 類

View Code
public  abstract  class 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; }
}

首先看下他的靜態方法先后調用 

View Code
public  static  void RegisterAllAreas()
{
    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方法。

View Code
internal  void CreateContextAndRegister(RouteCollection routes,  object state)
{
    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這些東東打包,以下構造方法。

View Code
public AreaRegistrationContext( string areaName, RouteCollection routes,  object 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的路由規則注冊。

View Code
     public  class AdminAreaRegistration : AreaRegistration
    {
         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方法是怎樣的。

View Code
public Route MapRoute( string name,  string url,  object defaults,  object constraints,  string[] namespaces)
{
     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規注注冊的一點區別。

route.DataTokens[ " area "] =  this.AreaName;
bool flag = (namespaces ==  null) || (namespaces.Length ==  0);
route.DataTokens[ " UseNamespaceFallback "] = flag;

這里我們基本可以確定,只要 Route 的 DataTokens 屬性里,有 area 這項值,就是一個area規則。
UseNamespaceFallback 這項值我以前的記錄里沒有,我記得以前整個mvc流程完了,好像也沒太看到這個屬性用在哪,這里先不管,不加也一樣。分析到再說吧。我們現在可以試一下,把area 里的 AdminAreaRegistration.cs 刪了。把area的路由規則放在Application_Start()里去注冊,如下:

View Code
         public  static  void RegisterRoutes(RouteCollection routes)
        {
            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的整個執行流程依次把多數源碼分析下去。其中結合了自己的一些見解,如果有什么不正確的地方還請指出。
轉載請注明出處,謝謝。

 


免責聲明!

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



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