至此爲止,咱們一直在使用ASP.NET MVC新項目隨帶的默認路由配置。如今咱們將深刻探討路由系統,並學習如何建立應用程序的自定義路由,以確保URL既是用戶友好又是搜索引擎可訪問的。正則表達式
路由的所有內容都是關於URL以及如何將URL做爲應用程序的外部輸入的。當使用其餘開發工具,如PHP、Web Form或是經典的ASP時,URL一般對應於磁盤上的物理文件。一個http://example.com/Products.aspx這樣的URL會致使執行負責處理該請求的名爲Products.aspx的文件。數據庫
經過使用URL路由,ASP.NET MVC解除了URL與物理文件的耦合。路由提供了一種把無擴展名的URL映射到控制器動做的方式,讓開發人員對URL方案有徹底的控制。服務器
在本章中,咱們將介紹路由概念及其與MVC應用程序的關係,還將簡要介紹它們如何用於ASP.NET Web Form項目。咱們將考察如何設計應用程序的URL方案,而後將這些概念運用於建立一個示例應用程序的路由。最後看看如何測試路由,以確保它們按預期工做。session
1、介紹URL路由架構
與將URL綁定到磁盤上的物理文件不一樣,ASP.NET MVC引入的URL路由的底層結構可以將URL映射到控制器動做,而無須在服務器上有物理文件做爲URL的目標。在本節中,咱們將考察新建MVC項目隨帶的默認路由的結構,以及這些路由是如何與控制器和動做的概念相關聯的。 mvc
1.默認路由框架
當建立一個新的ASP.NET MVC應用程序時,默認的項目模板會在Global.asax文件中調用一個名稱爲RegisterRoutes的方法。該方法負責爲應用程序配置路由,並定義了最初兩條路由——一條忽略路由和一條遵循{controller}/{action}/{id}模式的默認路由,以下所示。ide
路由是經過調用MapRoute方法而定義的,該方法有個過載。在這個例子中,默認路由是經過調用三個參數的過載來配置的。第一個參數是路由名("Default")。第二個參數是用來匹配URL的URL模式。本例中,URL模式被定義爲具備三個片斷——控制器、動做和ID。第三個參數是一個匿名類型,它爲這些片斷定義了默認值。工具
若是用戶訪問http://example.com/users/edit/5這樣的URL,這將匹配默認路由,由於它有三個片斷,如圖所示。性能
在這個例子中,字符串users映射到controller參數,edit映射到actio,而5映射到id。因爲這個URL顯然與路由相匹配,MVC框架會嘗試查找名稱爲UsersController的類、調用Edit方法,併爲其id參數傳遞5。若是找不到控制器或動做,框架會產生一個404錯誤。
添加到路由定義中的默認參數意味着URL沒必要精確地匹配三片斷URL模式。若是指定了默認控制器Home以及默認動做Index,當控制器片斷省略時,路由將默認控制器爲HomeController。一樣,若是動做片斷未指定,則路由會默認尋找Index動做。Id參數的默認值UrlParameter.Optional,意指不管是否指定第三個片斷,路由均可以被匹配。下表給出了幾個能匹配默認路由的例子。
URL | 路由參數 | 被選中的動做方法 |
http://example.com/Users/Edit/5 | Controller=User, Action=Edit, id=5 | UsersController.Edit(5) |
http://example.com/Users/Edit | Controller=User, Action=Edit | UsersController.Edit() |
http://example.com/Users | Controller=User, Action=Index | UsersController.Index() |
http://example.com | Controller=User, Action=Index | HomeController.Index() |
在IgnoreRoute方法中,模式{resource}.axd/{*pathInfo}確保文件擴展名爲.axd的任何URL不會被路由引擎所處理,這樣才能確保任何自定義HTTP處理程序(其擴展名爲.axd)以正確方式唄處理,而不會被路由引擎攔截。
2.入站與出站路由
入站路由(Inbound Routing):將URL映射到控制器或動做及任何附加參數。
出站路由(Outbound Routing):經過一組給定的路由數據(一般是控制器和動做)生成相應的URL。
上圖所示的入站路由描述了一個控制器動做的URL調用。HTTP請求進入ASP.NET管道,並經過ASP.NET MVC應用程序註冊的路由進行發送。每一個路由都有處理請求的機會,而匹配路由隨後會指定被使用的控制器和動做。
2、設計URL模式(schema)
1.創建簡單、整潔的URL
傳統URL:http://example.com/eventmanagement/events_by_month.aspx?year=2011&month=4
使用路由系統的URL:http://example.com/events/2011/04
這種URL帶來的好處是,其中的日期有了一種明確的層次格式。
2.創建可破解的URL
在設計URL方案時,考慮最終用戶爲了改變所顯示的數據要如何操縱或「破解」URL是有價值的。例如,也許能夠合理地假設,從如下URL移去參數「04」,可能表示2011年發生的所有事件:
http://example.com/events/2011/04
一樣的邏輯能夠造成下表所示的更全面的路由列表。
URL | 描述 |
http://example.com/events | 顯示所有事件 |
http://example.com/events/<year> | 顯示某年事件 |
http://example.com/events/<year>/<month> | 顯示某月事件 |
http://example.com/events/<year>/<month>/<day> | 顯示某日事件 |
讓URL模式具備這種靈活性是很棒的,但這可能會致使應用程序中具備大量潛在的URL。在創建應用程序視圖時,你老是要改出相應的導航。記住,可能在各個頁面上沒必要對每一個可能的URL組合都包含一個連接。在用戶試圖破解URL並使其生效時,讓用戶有一些驚喜的發現反而是件好事。
若是不但願用戶破解,可考慮使用鏈接字符來替代斜線,如/events/2008-04-01。
3.使用URL參數區分請求
讓咱們對此路由加以擴展,並容許按類別列出事件。從用戶的觀點來看,最有用的URL可能像這樣:
http://example.com/events/aspnet-usergroup-meeting
但如今有問題了!咱們已經有了一個與/events/<something>形式匹配的路由,用來列出特定年、月、日的事件,那麼如今如何用/events/<something>也匹配類別?第二個路由片斷如今意味着徹底不一樣的含義,這與現有的路由不協調了。若是把這種URL交給路由系統,它應該把這種參數可能做類別仍是日期?
幸運的是,ASP.NET MVC的路由系統容許咱們運用條件,使用正則表達式來確保路由只與某個模式的參數相匹配就夠了。這意味着咱們能夠只用一條路由,就能讓/events/2011-01-01形式的請求傳遞給按日期顯示事件的動做,而讓/events/asp-net-mvc-in-action形式的請求傳遞給按類別顯示事件的動做。
4.儘量避免暴露數據庫ID
一個用於託管開發人員事件的網站可能會定義這樣的URL:
http://example.com/events/87
87是從數據庫得到的每個對象都有一個主鍵形式的惟一標識符,可是除了數據庫管理員以外,數字87對任何人都毫無心義。所以,應該儘量避免在URL中使用數據庫生成的ID,儘可能讓它們有意義、可讀、易於理解。
http://example.com/events/houstonTechFest2010
5.考慮添加多餘信息
若是必須在URL中使用數據庫ID,可考慮添加除了使URL可讀外沒什麼目的的附加信息。
http://example.com/events/houstonTechFest2010/session-87
http://example.com/events/houstonTechFest2010/session-87/an-introduction-to-mvc
--------------------------------------------------------------------
搜索引擎優化(SEO)
當涉及網站的搜索引擎優化方面時,有必要提一提設計良好的URL的價值,在URL中放置一個相關的關鍵字提高搜索引擎排序。設計要點以下:
----------------------------------------------------------------------
3、在ASP.NET MVC中實現路由
默認項目模板建立了兩個默認路由,但你能夠不接受這兩個默認路由的限制,添加本身的路由,以實現徹底自定義的URL模式。如下將對此加以演示,以一個簡單的在線商店爲例,實現幾個路由。咱們將考察如何建立簡單、靜態的路由,以及建立更復雜的使用參數的路由和全匹配路由。
1.在線商店的URL模式(重要)
路由號 | URL | 描述 |
1 | http://example.com/ | 首頁,重定向到分類列表 |
2 | http://example.com/privacy | 顯示包含網站私有策略的靜態頁面 |
3 | http://example.com/products/<productcode> | 顯示相應產品代碼的產品詳情頁面 |
4 | http://example.com/products/<productcode>/buy | 將相應產品添加到購物籃 |
5 | http://example.com/basket | 顯示當前用戶的購物籃 |
6 | http://example.com/checkout | 啓動當前用戶的結算過程 |
注意:路由4中的URL不是設計給用戶看的,它經過表單遞交進行連接,在動做處理完成後會當即進行重定向,於是這種URL不會在地址欄中出現。
2.添加自定義靜態路由
路由1是由默認路由處理的。
路由2,是一個純靜態路由,將http://example.com/privacy映射到HomeController的Privacy動做。
routes.MapRoute("privacy_policcy","privacy", new { controller="Home", action="Privacy"});
警告:添加到路由表中的路由次序決定了查找匹配時的路由搜索順序。這意味着,源代碼中列出的路由,應當從帶有最具體條件的最高優先級下降到最低優先級,或全匹配路由。
3.添加自定義的動態路由
當有少許偏離通常規則的URL時,靜態路由是有用的。若是路由包含與頁面顯示的數據相關的信息,就須要動態路由了。
路由3和路由4是用兩個路由參數實現的:
routes.MapRoute("Product", "products/{productCode}/{action}", new { controller="Catalog", action="Show"});
兩個佔位符將匹配URL中用斜線分隔的片斷,productCode參數是必需的,但action是可選的。若是action未指定,該路由會默認指向CatalogController上的Show動做,並傳遞productCode參數。詳細代碼以下。
public class CatalogController : Controller { private ProductRepository _productRepository = new ProductRepository(); public ActionResult Show(string productCode) { var product = _productRepository.GetByCode(productCode); if (product == null) { return new NotFoundResult(); } return View(product); } }
實現一個執行時能生成HTTP 404的自定義動做結果:
public class NotFoundResult:ActionResult { public override void ExecuteResult(ControllerContext context) { context.HttpContext.Response.StatusCode = 404; new ViewResult { ViewName = "NotFound" }.ExecuteResult(context); } } }
NotFoundResult經過集成ActionResult,在其中提供了必須實現的ExecuteResult方法。該方法將響應狀態碼設置爲404,而後渲染一個名爲NotFound的視圖,該視圖位於Views/Shared目錄。
注意:HttpNotFoundResult動做,也將響應轉檯碼設置爲404,但它未提供顯示自定義錯誤頁面的機制,所以老是會給最終用戶顯現一個空屏。
最後,咱們能夠添加模式中的路由5和路由6。
routes.MapRoute{"catalog", "{action}", new { controller="Catalog" }, new { action=@"basket|checkout"});
這些路由幾乎是靜態路由,只不過它們是用一個參數和一個路由約束來實現的,以保持較少的路由數目。這麼作的主要緣由有兩個。第一,每一個請求都必須掃描路表進行匹配,因此大的路由集合會影響到性能。第二,路由越多,路由優先級問題出現的風險也越高。較少數目的路由規則更易於維護。
MapRoute方法的第四個參數包含了路由約束。約束參數是一個匿名類型形式的字典,能夠用於指定如何約束特定的路由參數。在本例中,咱們使用了一個正則表達式來指明,僅當片斷字符串爲「basket」或「checkout」時,才匹配action參數。這種約束可以適當地阻止把未知動做傳遞給控制器。
4.全匹路由
咱們如今已經添加了靜態和動態路由,以便爲網站的不一樣URL提供內容。但假設有一個與全部路由都不匹配的請求,會發生什麼?結果會拋出一個異常,這是實際應用程序中不但願發生的事情。爲了對此加以處理,咱們可使用與ASP.NET的錯誤處理基礎架構結合在一塊兒的全匹配路由。
咱們將添加一個全匹配路由,用它匹配還沒有被其餘路由匹配的任何URL,顯示HTTP404的錯誤消息,它應該是最後一條被定義的路由。
routes.MapRoute("404-catch-all", "{*catchall}", new { controller="Error", action="NotFound"});
值catchall爲全匹配路由要拾取的值提供了一個名稱。與規則路由參數不一樣,全匹配參數(以星號爲前綴)會捕獲包括正斜線在內的整個URL部分,正斜線一般用於分隔路由參數。在上述示例中,該路由被映射到ErrorController的NotFound動做。
public class ErrorController: Controller { public ActionResult NotFound() { return new NotFoundResult(); } }
如今,能夠刪去默認的{controller}/{action}/{id}路由,由於咱們已經徹底定製了路由,以匹配咱們的URL模式。或者,你也許會選擇保留它,以做爲訪問其餘控制器的一種默認方式。
4、使用路由系統生成URL
每當網站中須要一個URL時,咱們都要求框架給出,而不是採用硬編碼。咱們須要制定一種控制器、動做以及參數的組合,剩下的由ActionLink方法完成。ActionLink是MVC框架中HtmlHelper類上的一個擴展方法,它會生成一個插入了正確URL的完整的HTML<a>元素,該URL與傳遞進來的對象參數所指定的路由相匹配。如下是調用ActionLink的一個例子:
@Html.ActionLink ( "MVC3 in Action", "Show", "Catalog", new { productCode = "mvc-in-action" }, null )
第一個是超連接的顯示文本;第二個和第三個指定了要被連接到的動做和控制器;第四個採用了一個匿名類型形式的字典,以指定任意的附加路由參數;最後一個是仍以匿名類型形式指定的任意的附加HTML屬性。
使用前面定義的路由,這個例子會生成一個連接,指向CatalogController上的Show動做,並帶有爲productCode指定的附加參數。如下是其輸出:
<a href="/products/mvc-in-action">MVC3 in Action</a>
相似地,若是使用HtmlHelper的BeginForm方法來創建表單標籤,它會爲你生成URL。有時,可以將路由部分未指定的參數傳遞給動做時有用的:
@Html.ActionLink ( "MVC3 in Action", "Show", "Catalog", new { productCode = "mvc-in-action", currency="USD" }, null )
若是該參數與路由中的某個部分匹配,它將成爲URL的一部分。不然,它將被附加到查詢字符串。好比,如下是上述代碼生成的連接:
<a href="/products/mvc-in-action?currency=USD">MVC3 in Action</a>
在使用ActionLink時,被選中的路由是路由集合中所定義的第一個匹配路由。大多數狀況下,這是足夠的,但若是你但願請求一條特定的路由,可使用RouteLink,它接受一個標識被請求路由的參數,像這樣:
@Html.RouteLink ( "MVC3 in Action", "Show", "Catalog", new { productCode = "mvc-in-action" }, null )
這個代碼將查找一個帶有product名稱的路由,而不是指定的控制器和動做。
有時候你須要得到一個URL,但不是爲了連接或表單。這一般發生在編寫Ajax代碼須要設置一個請求URL時。UrlHelper類可以直接生成URL,由ActionLink方法和其餘方法所使用。如下是一個例子:
@Url.Action ( "Show", "Catalog", new { productCode = "mvc-in-action" } )
這個代碼也返回/products/mvc-in-action,但沒有任何包圍標籤。