學習ASP.NET + MVC(二)

一.摘要

  本篇文章從基礎到深入的介紹ASP.NET MVC中的Routing組件. Routing翻譯過來是"路由選擇", 負責ASP.NET MVC的第一個工作:識別URL, 將一個Url請求"路由"給Controller. 由於今天下午參加了博客園北京俱樂部的聚會, 所以本篇文章的完工時間晚了點, 還好也是在今天發表, 總算兌現了"每日一篇"的承諾. 不久丁學就會發布北京博客園聚會活動的資料了, 我在這裏先預告了!

  二.承上啓下

  第一篇文章中我們已經學會了如何使用ASP.NET MVC, 雖然其中還有很多的細節沒有深入瞭解, 但是對基本的處理流程已經有了認識:來了一個Url請求, 從中找到Controller和Action的值, 將請求傳遞給Controller處理. Controller獲取Model數據對象, 並且將Model傳遞給View, 最後View負責呈現頁面.

  而Routing的作用就是負責分析Url, 從Url中識別參數, 如圖:

學習ASP.NET MVC(二) 識別URL的Routing組件

  這一講就讓我們細緻的瞭解System.Web.Routing及其相關的擴展知識.

  三.Routing的作用

  第一講中實例的首頁地址是: localhost/home/index

  我們發現訪問上面的地址, 最後會傳遞給 HomeController中名爲index的action(即HomeController類中的index方法).

  當然服務器端不會自己去實現這個功能,  關鍵點就是在Global.asax.cs文件中的下列代碼:

    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 = "" } // Parameter defaults
      );

    }

    protected void Application_Start()
    {
      RegisterRoutes(RouteTable.Routes);
    }

  回來看我們的Url: localhost/home/index

  localhost是域名, 所以首先要去掉域名部分: home/index

  對應了上面代碼中的這種URL結構: {controller}/{action}/{id}

  因爲我們建立了這種Url結構的識別規則, 所以能夠識別出 Controller是home, action是index, id沒有則爲默認值"".

  這就是Routing的第一個作用:

  1.從Url中識別出數據.比如controller,action和各種參數.

  如果跟蹤程序, 接下來我們會跳轉到HomeController中的Index()方法.  這是Routing內部爲實現的第二個作用:

  2.根據識別出來的數據, 將請求傳遞給Controller和Action.

  但從實例中我們並不知道Routing如何做的這部份工作.第五部分我做了深入講解.

  四.Routing的使用

  在分析Routing的實現原理前, 先學習如何使用Routing爲ASP.NET MVC程序添加路由規則.

  1. 使用MapRoute()方法.

  這是最簡單的爲ASP.NET MVC添加識別規則的方法.此方法有如下重載:

MapRoute( string name, string url);
MapRoute( string name, string url, object defaults);
MapRoute( string name, string url, string[] namespaces);
MapRoute( string name, string url, object defaults, object constraints);
MapRoute( string name, string url, object defaults, string[] namespaces);
MapRoute( string name, string url, object defaults, object constraints, string[] namespaces);

 

  name參數:

  規則名稱, 可以隨意起名.當時不可以重名,否則會發生錯誤:

  路由集合中已經存在名爲“Default”的路由。路由名必須是唯一的。

  url參數:

  url獲取數據的規則, 這裏不是正則表達式,  將要識別的參數括起來即可, 比如: {controller}/{action}

  最少只需要傳遞name和url參數就可以建立一條Routing(路由)規則.比如實例中的規則完全可以改爲:

      routes.MapRoute(
          "Default",
          "{controller}/{action}");

  defaults參數:

  url參數的默認值.如果一個url只有controller: localhost/home/

  而且我們只建立了一條url獲取數據規則: {controller}/{action}

  那麼這時就會爲action參數設置defaults參數中規定的默認值. defaults參數是Object類型,所以可以傳遞一個匿名類型來初始化默認值:

new { controller = "Home", action = "Index" }

  實例中使用的是三個參數的MapRoute方法:

      routes.MapRoute(
        "Default",                       // Route name
        "{controller}/{action}/{id}",              // URL with parameters
        new { controller = "Home", action = "Index", id = "" } // Parameter defaults
      );

  constraints參數:

  用來限定每個參數的規則或Http請求的類型.constraints屬性是一個RouteValueDictionary對象,也就是一個字典表, 但是這個字典表的值可以有兩種:

  用於定義正則表達式的字符串。正則表達式不區分大小寫。

  一個用於實現 IRouteConstraint 接口且包含 Match 方法的對象。

  通過使用正則表達式可以規定參數格式,比如controller參數只能爲4位數字:

new { controller = @"/d{4}"}

 

 

  通過第IRouteConstraint 接口目前可以限制請求的類型.因爲System.Web.Routing中提供了HttpMethodConstraint類, 這個類實現了IRouteConstraint 接口. 我們可以通過爲RouteValueDictionary字典對象添加鍵爲"httpMethod", 值爲一個HttpMethodConstraint對象來爲路由規則添加HTTP 謂詞的限制, 比如限制一條路由規則只能處理GET請求:

httpMethod = new HttpMethodConstraint( "GET", "POST" )

 

  完整的代碼如下:

      routes.MapRoute(
        "Default",                       // Route name
        "{controller}/{action}/{id}",              // URL with parameters
        new { controller = "Home", action = "Index", id = "" }, // Parameter defaults
        new { controller = @"/d{4}" , httpMethod = new HttpMethodConstraint( "GET", "POST" ) }
      );

  當然我們也可以在外部先創建一個RouteValueDictionary對象在作爲MapRoute的參數傳入, 這只是語法問題.

  namespaces參數:

  此參數對應Route.DataTokens屬性. 官方的解釋是:

  獲取或設置傳遞到路由處理程序但未用於確定該路由是否匹配 URL 模式的自定義值。

  我目前不知道如何使用. 請高手指點

  2.MapRoute方法實例

  下面通過實例來應用MapRoute方法. 對於一個網站,爲了SEO友好,一個網址的URL層次不要超過三層:

  localhost/{頻道}/{具體網頁}

  其中域名第一層, 頻道第二層, 那麼最後的網頁就只剩下最後一層了. 如果使用默認實例中的"{controller}/{action}/{其他參數}"的形式會影響網站的SEO.

  假設我們的網站結構如下:

學習ASP.NET MVC(二) 識別URL的Routing組件

  下面以酒店頻道爲例, 是我創建的Routing規則:

    public static void RegisterRoutes(RouteCollection routes)
    {
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

      #region 酒店頻道部分
      // hotels/list-beijing-100,200-3
      routes.MapRoute(
        "酒店列表頁",
        "hotels/{action}-{city}-{price}-{star}",
        new { controller = "Hotel", action = "list", city = "beijing", price="-1,-1", star="-1" },
        new { city=@"[a-zA-Z]*",price=@"(/d)+/,(/d)+", star="[-1-5]"}
        );

      //hotels/所有匹配
      routes.MapRoute(
        "酒店首頁",
        "hotels/{*values}",
        new { controller = "Hotel", action = "default", hotelid = "" }
        );
      #endregion

      //網站首頁.
      routes.MapRoute(
         "網站首頁",
         "{*values}",
         new { controller = "Home", action = "index"}
         ); 
    }

 

  實現的功能:

  (1)訪問 localhost/hotels/list-beijing-100,200-3 會訪問酒店頻道的列表頁,並傳入查詢參數

  (2)訪問 localhost/hotels 下面的任何其他頁面地址, 都會跳轉到酒店首頁.

  (3)訪問 localhost 下面的任何地址, 如果未匹配上面2條, 則跳轉到首頁.

  簡單總結:

  (1)Routing規則有順序(按照添加是的順序), 如果一個url匹配了多個Routing規則, 則按照第一個匹配的Routing規則執行.

  (2)由於上面的規則, 要將具體頻道的具體頁面放在最上方, 將頻道首頁 和 網站首頁 放在最下方.

  (3) {*values} 表示後面可以使任意的格式.

  3.使用Route類

  MapRoute方法雖然簡單, 但是他是本質也是通過創建Route類的實例, 爲RouteCollection集合添加成員.

  下載最新版本的MSDN-Visual Studio 20008 SP1, 已經可以找到Route類的說明.

  創建一個Route類實例,最關鍵的是爲以下幾個屬性賦值:

 

屬性名稱 說明 舉例
Constraints 獲取或設置爲 URL 參數指定有效值的表達式的詞典。 {controller}/{action}/{id}
DataTokens 獲取或設置傳遞到路由處理程序但未用於確定該路由是否匹配 URL 模式的自定義值。 new RouteValueDictionary { { "format", "short" } }
Defaults 獲取或設置要在 URL 不包含所有參數時使用的值。 new { controller = "Home", action = "Index", id = "" }
RouteHandler 獲取或設置處理路由請求的對象。 new MvcRouteHandler()
Url 獲取或設置路由的 URL 模式。 new { controller = @"[^/.]*" }

  這些屬性除了RouteHandler以外, 其他的都對應MapRoute方法的參數.RouteHandler是實現了IRouteHandler接口的對象.關於此接口的作用在第五部分Routing深入解析中做講解.

  五.Routing深入解析

  對於一個一般開發人員來說, 上面的知識已經完全足夠你使用ASP.NET MVC時使用Routing了.

  接下來的部分我將深入Routing的機制講解Routing的高級應用.但是因爲是"高級應用", 加上這篇文章已經太長了, 再加上馬上今天就過去了, "每日一篇"的承諾一定要兌現的, 所以不會對所有細節進行講解. 或者也可以略過此部分.

  Routing如何將請求傳遞給Controller?上面講解Routing作用的時候, 我們就分析出Routing會將請求傳遞給Controller, 但是Routing如何做的這部份工作我們卻看不到.關鍵在於MapRoute()這個方法封裝了具體的細節.

  雖然MapRoute方法是RouteCollection對象的方法,但是卻被放置在System.Web.Mvc程序集中, 如果你的程序只引用了System.Web.Routing, 那麼RouteCollection對象是不會有MapRoute方法的. 但是如果你同又引用了System.Web.Mvc, 則在mvc的dll中爲RouteCollection對象添加了擴展方法:

    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);

  RouteCollection是一個集合,他的每一項應該是一個Route對象. 但是我們使用MapRoute時並沒有創建這個對象, 這是因爲當我們將MapRoute方法需要的參數傳入時, 在方法內部會根據參數創建一個Route對象:

    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 route = new Route(url, new MvcRouteHandler()) {
        Defaults = new RouteValueDictionary(defaults),
        Constraints = new RouteValueDictionary(constraints)
      };

      if ((namespaces != null) && (namespaces.Length > 0)) {
        route.DataTokens = new RouteValueDictionary();
        route.DataTokens["Namespaces"] = namespaces;
      }

      routes.Add(name, route);

      return route;
    }

  上面就是MapRoute方法的實現, 至於在創建Route對象時第二個參數是一個MvcRouteHandler, 它是一個實現了IRouteHandler接口的類. IRouteHandler十分簡單隻有一個方法:

IHttpHandler GetHttpHandler(RequestContext requestContext);

 

  參數是一個RequestContext 類實例, 這個類的結構也很簡單:

  public class RequestContext
  {
    public RequestContext(HttpContextBase httpContext, RouteData routeData);

    public HttpContextBase HttpContext { get; }
    public RouteData RouteData { get; }
  }

其中的一個屬性RouteData就包含了Routing根據Url識別出來各種參數的值, 其中就有Controller和Action的值.

  歸根結底, ASP.NET MVC最後還是使用HttpHandler處理請求. ASP.NET MVC定義了自己的實現了IHttpHandler接口的Handler:MvcHandler, 因爲MvcRouteHandler的GetHttpHandler方法最後返回的就是MvcHandler. 

  MvcHandler的構造函數需要傳入RequestContext 對象, 也就是傳入了所有的所有需要的數據, 所以最後可以找到對應的Controller和Action, 已經各種參數.

  六.測試Routing

  因爲一個Url會匹配多個routing規則, 最後常常會遇到規則寫錯或者順序不對的問題.於是我們希望能夠看到Url匹配Routing的結果.

  其中最簡單的辦法就是使用RouteDebug輔助類. 這個類需要單獨下載dll組件, 我將此組件的下載放在了博客園上:

  http://files.cnblogs.com/zhangziqiu/RouteDebug-Binary.zip

  解壓縮後是一個DLL文件, 將這個DLL文件添加到項目中並且添加引用.

  使用方法很簡單, 只需要在Application_Start方法中添加一句話:

RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);

  比如下面是我的示例中的代碼:

    protected void Application_Start()
    {
      RegisterRoutes(RouteTable.Routes);
      RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
    }

 

  現在你訪問任何URL, 都會出現RouteDebug頁面, 如下:

學習ASP.NET MVC(二) 識別URL的Routing組件

  其中不僅有你的所有Routing規則, 還顯示了是否匹配.並且按照順序列出. 還有識別的參數列表.

  當你不想測試Routing規則的時候則註釋掉這一段, 即可回覆跳轉到View對象上.

  七.總結

  本文講解了ASP.NET MVC中一個關鍵的組件:Routing的使用. System.Web.Routing在Framework3.5 SP1中已經集成, 也就是說雖然我們還沒有ASP.NET MVC的正式版, 但是Routing組件卻已經提早發佈了. 因爲Routing是一個相對獨立的組件, 不僅能和ASP.NET MVC配額使用, 也可以用於任何需要URL路由的項目. 另外Routing的作用和Url重寫(Url Rewrite)是有區別的, 你會發現Routing和Url Rewrite相比其實很麻煩, 無論是添加規則還是傳遞參數.對UrlRewite感興趣的可以去尋找UrlRewrite.dll這個組件, 很簡單很強大, 有關兩者的異同以及如何使用UrlRewrite這裏不在多說了.

  另外今天博客園北京俱樂部聚會, 聽了2位大師的講座, 見到了至今沒記住名字和樣子的同行們, 很是激動和興奮. 期待俱樂部領導將活動照片和資料放出!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章