1. 程式人生 > >從零開始學習 ASP.NET MVC 1.0 (二) 識別URL的Routing元件

從零開始學習 ASP.NET MVC 1.0 (二) 識別URL的Routing元件

《從零開始學習ASP.NET MVC 1.0》 文章導航

一.摘要

本篇文章從基礎到深入的介紹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中識別引數, 如圖:

image

這一講就讓我們細緻的瞭解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.

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

    image

    下面以酒店頻道為例, 是我建立的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元件, 我將此元件的下載放在了部落格園上:

    解壓縮後是一個DLL檔案, 將這個DLL檔案新增到專案中並且新增引用.

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

    RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);


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

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


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

    image

    其中不僅有你的所有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這裡不在多說了.

    本文的示例下載地址: