《從零開始學習ASP.NET MVC 1.0》 文章導航
(一) 開天辟地入門篇(二) 識別URL的Routing組件(三) Controller/Action 深入解析與應用實例(四) View/Model 全解(五) ViewEngine 深入解析與應用實例一.摘要
本篇文章從基礎(chǔ)到深入的介紹ASP.NET MVC中的Routing組件. Routing翻譯過來是"路由選擇", 負責ASP.NET MVC的第一個工作:識別URL, 將一個Url請求"路由"給Controller.
二.承上啟下
第一篇文章中我們已經(jīng)學會了如何使用ASP.NET MVC, 雖然其中還有很多的細節(jié)沒有深入了解, 但是對基本的處理流程已經(jīng)有了認識:來了一個Url請求, 從中找到Controller和Action的值, 將請求傳遞給Controller處理. Controller獲取Model數(shù)據(jù)對象, 并且將Model傳遞給View, 最后View負責呈現(xiàn)頁面.
而Routing的作用就是負責分析Url, 從Url中識別參數(shù), 如圖:
這一講就讓我們細致的了解System.Web.Routing及其相關(guān)的擴展知識.
三.Routing的作用
第一講中實例的首頁地址是: localhost/home/index
我們發(fā)現(xiàn)訪問上面的地址, 最后會傳遞給 HomeController中名為index的action(即HomeController類中的index方法).
當然服務器端不會自己去實現(xiàn)這個功能, 關(guān)鍵點就是在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結(jié)構(gòu): {controller}/{action}/{id}
因為我們建立了這種Url結(jié)構(gòu)的識別規(guī)則, 所以能夠識別出 Controller是home, action是index, id沒有則為默認值"".
這就是Routing的第一個作用:
1.從Url中識別出數(shù)據(jù).比如controller,action和各種參數(shù).
如果跟蹤程序, 接下來我們會跳轉(zhuǎn)到HomeController中的Index()方法. 這是Routing內(nèi)部為實現(xiàn)的第二個作用:
2.根據(jù)識別出來的數(shù)據(jù), 將請求傳遞給Controller和Action.
但從實例中我們并不知道Routing如何做的這部份工作.第五部分我做了深入講解.
四.Routing的使用
在分析Routing的實現(xiàn)原理前, 先學習如何使用Routing為ASP.NET MVC程序添加路由規(guī)則.
1. 使用MapRoute()方法.
這是最簡單的為ASP.NET MVC添加識別規(guī)則的方法.此方法有如下重載:
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參數(shù):
規(guī)則名稱, 可以隨意起名.當時不可以重名,否則會發(fā)生錯誤:
路由集合中已經(jīng)存在名為“Default”的路由。路由名必須是唯一的。
url參數(shù):
url獲取數(shù)據(jù)的規(guī)則, 這里不是正則表達式, 將要識別的參數(shù)括起來即可, 比如: {controller}/{action}
最少只需要傳遞name和url參數(shù)就可以建立一條Routing(路由)規(guī)則.比如實例中的規(guī)則完全可以改為:
routes.MapRoute( "Default", "{controller}/{action}");defaults參數(shù):
url參數(shù)的默認值.如果一個url只有controller: localhost/home/
而且我們只建立了一條url獲取數(shù)據(jù)規(guī)則: {controller}/{action}
那么這時就會為action參數(shù)設(shè)置defaults參數(shù)中規(guī)定的默認值. defaults參數(shù)是Object類型,所以可以傳遞一個匿名類型來初始化默認值:
new { controller = "Home", action = "Index" }
實例中使用的是三個參數(shù)的MapRoute方法:
routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = "" } // Parameter defaults );constraints參數(shù):
用來限定每個參數(shù)的規(guī)則或Http請求的類型.constraints屬性是一個RouteValueDictionary對象,也就是一個字典表, 但是這個字典表的值可以有兩種:
用于定義正則表達式的字符串。正則表達式不區(qū)分大小寫。
一個用于實現(xiàn) IRouteConstraint 接口且包含
Match 方法的對象。
通過使用正則表達式可以規(guī)定參數(shù)格式,比如controller參數(shù)只能為4位數(shù)字:
new { controller = @"\d{4}"}
通過第IRouteConstraint 接口目前可以限制請求的類型.因為System.Web.Routing中提供了HttpMethodConstraint類, 這個類實現(xiàn)了IRouteConstraint 接口. 我們可以通過為RouteValueDictionary字典對象添加鍵為"httpMethod", 值為一個HttpMethodConstraint對象來為路由規(guī)則添加HTTP 謂詞的限制, 比如限制一條路由規(guī)則只能處理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" ) } );
當然我們也可以在外部先創(chuàng)建一個RouteValueDictionary對象在作為MapRoute的參數(shù)傳入, 這只是語法問題.
namespaces參數(shù):
此參數(shù)對應Route.DataTokens屬性. 官方的解釋是:
獲取或設(shè)置傳遞到路由處理程序但未用于確定該路由是否匹配 URL 模式的自定義值。
我目前不知道如何使用. 請高手指點
2.MapRoute方法實例
下面通過實例來應用MapRoute方法. 對于一個網(wǎng)站,為了SEO友好,一個網(wǎng)址的URL層次不要超過三層:
localhost/{頻道}/{具體網(wǎng)頁}
其中域名第一層, 頻道第二層, 那么最后的網(wǎng)頁就只剩下最后一層了. 如果使用默認實例中的"{controller}/{action}/{其他參數(shù)}"的形式會影響網(wǎng)站的SEO.
假設(shè)我們的網(wǎng)站結(jié)構(gòu)如下:
下面以酒店頻道為例, 是我創(chuàng)建的Routing規(guī)則:
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 //網(wǎng)站首頁. routes.MapRoute( "網(wǎng)站首頁", "{*values}", new { controller = "Home", action = "index"} ); }
實現(xiàn)的功能:
(1)訪問 localhost/hotels/list-beijing-100,200-3 會訪問酒店頻道的列表頁,并傳入查詢參數(shù)
(2)訪問 localhost/hotels 下面的任何其他頁面地址, 都會跳轉(zhuǎn)到酒店首頁.
(3)訪問 localhost 下面的任何地址, 如果未匹配上面2條, 則跳轉(zhuǎn)到首頁.
簡單總結(jié):
(1)Routing規(guī)則有順序(按照添加是的順序), 如果一個url匹配了多個Routing規(guī)則, 則按照第一個匹配的Routing規(guī)則執(zhí)行.
(2)由于上面的規(guī)則, 要將具體頻道的具體頁面放在最上方, 將頻道首頁 和 網(wǎng)站首頁 放在最下方.
(3) {*values} 表示后面可以使任意的格式.
3.使用Route類
MapRoute方法雖然簡單, 但是他是本質(zhì)也是通過創(chuàng)建Route類的實例, 為RouteCollection集合添加成員.
下載最新版本的MSDN-Visual Studio 20008 SP1, 已經(jīng)可以找到Route類的說明.
創(chuàng)建一個Route類實例,最關(guān)鍵的是為以下幾個屬性賦值:
屬性名稱 說明 舉例
Constraints 獲取或設(shè)置為 URL 參數(shù)指定有效值的表達式的詞典。 {controller}/{action}/{id}
DataTokens 獲取或設(shè)置傳遞到路由處理程序但未用于確定該路由是否匹配 URL 模式的自定義值。 new RouteValueDictionary { { "format", "short" } }
Defaults 獲取或設(shè)置要在 URL 不包含所有參數(shù)時使用的值。 new { controller = "Home", action = "Index", id = "" }
RouteHandler 獲取或設(shè)置處理路由請求的對象。 new MvcRouteHandler()
Url 獲取或設(shè)置路由的 URL 模式。 new { controller = @"[^\.]*" }
這些屬性除了RouteHandler以外, 其他的都對應MapRoute方法的參數(shù).RouteHandler是實現(xiàn)了IRouteHandler接口的對象.關(guān)于此接口的作用在第五部分Routing深入解析中做講解.
五.Routing深入解析
對于一個一般開發(fā)人員來說, 上面的知識已經(jīng)完全足夠你使用ASP.NET MVC時使用Routing了.
接下來的部分我將深入Routing的機制講解Routing的高級應用.但是因為是"高級應用", 加上這篇文章已經(jīng)太長了, 再加上馬上今天就過去了, "每日一篇"的承諾一定要兌現(xiàn)的, 所以不會對所有細節(jié)進行講解. 或者也可以略過此部分.
Routing如何將請求傳遞給Controller?上面講解Routing作用的時候, 我們就分析出Routing會將請求傳遞給Controller, 但是Routing如何做的這部份工作我們卻看不到.關(guān)鍵在于MapRoute()這個方法封裝了具體的細節(jié).
雖然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時并沒有創(chuàng)建這個對象, 這是因為當我們將MapRoute方法需要的參數(shù)傳入時, 在方法內(nèi)部會根據(jù)參數(shù)創(chuàng)建一個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方法的實現(xiàn), 至于在創(chuàng)建Route對象時第二個參數(shù)是一個MvcRouteHandler, 它是一個實現(xiàn)了IRouteHandler接口的類. IRouteHandler十分簡單只有一個方法:
IHttpHandler GetHttpHandler(RequestContext requestContext);
參數(shù)是一個RequestContext 類實例, 這個類的結(jié)構(gòu)也很簡單:
public class RequestContext { public RequestContext(HttpContextBase httpContext, RouteData routeData); public HttpContextBase HttpContext { get; } public RouteData RouteData { get; } }
其中的一個屬性RouteData就包含了Routing根據(jù)Url識別出來各種參數(shù)的值, 其中就有Controller和Action的值.
歸根結(jié)底, ASP.NET MVC最后還是使用HttpHandler處理請求. ASP.NET MVC定義了自己的實現(xiàn)了IHttpHandler接口的Handler:MvcHandler, 因為MvcRouteHandler的GetHttpHandler方法最后返回的就是MvcHandler.
MvcHandler的構(gòu)造函數(shù)需要傳入RequestContext 對象, 也就是傳入了所有的所有需要的數(shù)據(jù), 所以最后可以找到對應的Controller和Action, 已經(jīng)各種參數(shù).
六.測試Routing
因為一個Url會匹配多個routing規(guī)則, 最后常常會遇到規(guī)則寫錯或者順序不對的問題.于是我們希望能夠看到Url匹配Routing的結(jié)果.
其中最簡單的辦法就是使用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); }
現(xiàn)在你訪問任何URL, 都會出現(xiàn)RouteDebug頁面, 如下:
其中不僅有你的所有Routing規(guī)則, 還顯示了是否匹配.并且按照順序列出. 還有識別的參數(shù)列表.
當你不想測試Routing規(guī)則的時候則注釋掉這一段, 即可回復跳轉(zhuǎn)到View對象上.
七.總結(jié)
本文講解了ASP.NET MVC中一個關(guān)鍵的組件:Routing的使用. System.Web.Routing在Framework3.5 SP1中已經(jīng)集成, 也就是說雖然我們還沒有ASP.NET MVC的正式版, 但是Routing組件卻已經(jīng)提早發(fā)布了. 因為Routing是一個相對獨立的組件, 不僅能和ASP.NET MVC配額使用, 也可以用于任何需要URL路由的項目. 另外Routing的作用和Url重寫(Url Rewrite)是有區(qū)別的, 你會發(fā)現(xiàn)Routing和Url Rewrite相比其實很麻煩, 無論是添加規(guī)則還是傳遞參數(shù).對UrlRewite感興趣的可以去尋找UrlRewrite.dll這個組件, 很簡單很強大, 有關(guān)兩者的異同以及如何使用UrlRewrite這里不在多說了.
本文的示例下載地址:
http://files.cnblogs.com/zhangziqiu/Demo-2.rar