來自 張子陽 http://www.cnblogs.com/JimmyZhang/archive/2009/03/08/1406512.html
原文連結:http://www.asp.net/learn/mvc/
在這篇教程中,我將為你介紹每個ASP.NET MVC應用程式都具有的一個重要功能,稱作ASP.NET路由(ASP.NET Routing)。ASP.NET路由模組負責將即將到來的瀏覽器請求映射到特定的MVC控制器動作。學完這篇教程之後,你將會理解標準的路由表是如何將請求映射到控制器動作的。
1. 理解預設路由表
當你建立一個新的ASP.NET MVC應用程式時,應用程式已經被配置為使用ASP.NET路由。ASP.NET路由在兩個地方設定。
第一點,在你的應用程式Web設定檔(Web.config檔案)中啟用ASP.NET路由。在設定檔中有四個節點與路由有關:sytem.web.httpModules節,system.web.httpHandlers節,system.webserver.modules節,以及system.webserver.handlers節。特別要小心不要刪除了這些節點,因為沒有它們路由將不能工作。
第二點,也是更為重要的一點,在應用程式的Global.asax檔案中建立了一個路由表。Global.asax檔案是一個特殊的檔案,它包含了作用於ASP.NET應用程式生命週期事件的事件處理常式。路由表在Application Start事件期間建立。
代碼清單1中的檔案包含了一個ASP.NET MVC應用程式的預設Global.asax檔案。
代碼清單1 - Global.asax.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace MvcApplication1 {
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
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 = "" } // Parameter defaults );
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
}
當一個MVC應用程式首次運行時,會調用Application_Start()方法。這個方法隨後調用了RegisterRoutes()方法。RegisterRoutes()方法建立了路由表。
預設的路由表包含了一個路由(名叫Default)。Default路由將URL的第一部分映射到控制器名,URL的第二部分映射到控制器動作,第三個部分映射到一個叫做id的參數。
假設你在瀏覽器的地址欄輸入了下面的URL:
/Home/Index/3
預設的路由將這個URL映射為下面的參數:
Controller = Home
Action = Index
id = 3
當你請求URL /Home/Index/3時,將會執行下面的代碼:
HomeController.Index(3)
Default路由包含了所有三個參數的預設值。如果你不提供控制器,那麼控制器參數預設值為Home。如果你不提供動作,動作參數預設為值Index。最後,如果你不提供id,id參數預設為空白字串。
讓我們看看幾個例子,Default路由是如何將URL映射到控制器動作的。設想你在瀏覽器地址欄輸入了下面的URL:
/Home
由於Default路由參數的預設值,輸入這個URL將會調用代碼清單2中的HomeController類的Index()方法。
代碼清單2 - HomeController.cs
using System.Web.Mvc;
namespace MvcApplication1.Controllers {
[HandleError]
public class HomeController : Controller
{
public ActionResult Index(string id)
{
return View();
}
}
}
在代碼清單2中,HomeController類包含了一個叫做Index()的方法,它接受一個叫做Id的參數。URL /Home將會導致調用Index()方法,並使用Null 字元串作為Id參數的值。
出於MVC架構調用控制器動作的方式,URL /Home也符合代碼清單3中HomeController類的Index()方法。
代碼清單3 - HomeController.cs(不含參數的Index動作)
using System.Web.Mvc;
namespace MvcApplication1.Controllers {
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
}
代碼清單3中的Index()方法不接受任何的參數。URL /Home將會導致調用這個Index()方法。URL /Home/Index/3也會調用這個方法(Id被忽略)。
URL /Home也會符合代碼清單4中HomeController類的Index()方法。
代碼清單4 - HomeController.cs(使用可空參數的Index動作)
using System.Web.Mvc;
namespace MvcApplication1.Controllers {
[HandleError]
public class HomeController : Controller
{
public ActionResult Index(int? id)
{
return View();
}
}
}
在代碼清單4中,Index()方法擁有一個整數參數。因為這個參數是一個可空參數(可以擁有Null值),因此可以調用Index()而不會引發錯誤。
最後,使用URL /Home 調用代碼清單5中的Index()方法將會引發一個異常,因為Id參數並非一個可空參數。如果你試圖調用Index()方法,那麼你將會獲得一個圖1中所示的錯誤。
代碼清單5 - HomeController.cs(含有Id參數的Index動作)
using System.Web.Mvc;
namespace MvcApplication1.Controllers {
[HandleError]
public class HomeController : Controller
{
public ActionResult Index(int id)
{
return View();
}
}
}
圖01: 調用一個期望參數值的控制器動作
另一方面,URL /Home/Index/3 能夠與代碼清單5中的Index控制器動作很好地工作。/Home/Index/3請求將會引發調用含有一個Id的Index()方法,且該Id值為3.
這篇教程的目的是為你提供一個ASP.NET路由的簡短介紹。我們仔細查看了預設的路由表,它在你建立新的ASP.NET MVC應用程式時獲得。你學習了預設的路由表如何將URL映射到控制器動作。
2.建立自訂路由
在這篇教程中,你會學習到如何為ASP.NET MVC應用程式添加自訂路由。你會學習如何將Global.asax檔案中的預設路由表修改為自訂路由。
對於簡單的ASP.NET MVC應用程式,預設的路由表已經可以很好的完成工作了。然而,你可能發現會存在特定的路由需求。在這種情況下,你可以建立一個自訂路由。
設想一下,舉個例子,你正在建立一個部落格應用程式。你可能想要像這樣處理即將到來的請求:
/Archive/12-25-2009
當使用者輸入這一請求,你想要返回對應於日期12/25/2009的部落格條目。為了處理這種類型的請求,你需要建立一個自訂路由。
代碼清單1中的Global.asax包含了一個新的自訂路由,命名為了Blog,它處理了類似/Archive/條目日期 這樣的請求。
代碼清單1 - Global.asax(含有自訂路由)
using System.Web.Mvc; using System.Web.Routing;
namespace MvcApplication1 {
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Blog", // Route name
"Archive/{entryDate}", // URL with parameters
new {controller = "Archive", action = "Entry" } // Parameter defaults );
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);
}
}
}
添加到路由表中的路由順序非常重要。我們的新自訂Blog路由在現有的Default路由前面。如果你將這個順序顛倒過來,那麼Default路由將總是被調用,而不是自訂路由。
自訂Blog路由匹配任何以/Archive/作為開始的請求。因此,它匹配所有下面的URL:
/Archive/12-25-2009
/Archive/10-6-2004
/Archive/apple
自訂路由將即將到來的請求映射到名為Archive的控制器,並且調用了Entry()動作。當調用Entry()方法時,條目日期作為entryDate參數進行了傳遞。
代碼清單2 - ArchiveController.cs
using System;
using System.Web.Mvc;
namespace MvcApplication1.Controllers {
public class ArchiveController : Controller
{
public string Entry(DateTime entryDate)
{
return "You requested the entry from " + entryDate.ToString();
}
}
}
注意到代碼清單2中的Entry()方法接受一個DateTime類型的參數。MVC架構非常的聰明,足以自動地將URL中的條目日期轉換為DateTime值。如果URL中的條目日期參數不能轉換為DateTime,將會引發錯誤(1)。
圖1 - 轉換參數時的錯誤
這篇教程的目的是示範如何建立自訂路由。你學習了如何在Global.asax檔案的路由表中添加自訂路由,該路由代表著部落格條目。我們討論了如何將對部落格條目的請求映射到名為ArchiveController的控制器,和名為Entry()的控制器動作上。
3.建立路由約束
你可以使用路由約束來限制匹配特定路由的瀏覽器請求。可以使用Regex來指定一個路由約束。
舉個例子,假設你已經在Global.asax檔案中定義了一個路由。
代碼清單1 - Global.asax.cs
routes.MapRoute( "Product", "Product/{productId}",
new {controller="Product", action="Details"} );
代碼清單1包含一個叫做Product的路由。你可以使用Product路由將瀏覽器請求映射到代碼清單2中的ProductController。
代碼清單2 - ControllersProductController.cs
using System.Web.Mvc;
namespace MvcApplication1.Controllers {
public class ProductController : Controller
{
public ActionResult Details(int productId)
{
return View();
}
}
}
注意到Product控制器公布的Details()動作接受一個叫做productId的參數。這個參數是一個整數參數。
定義在代碼清單1中的路由將會匹配下面的任意URL:
不幸的是,路由也會匹配下面的URL:
- /Product/blah
- /Product/apple
因為Details()動作期望的是一個整數值,發起一個含有非整數值的請求將會導致錯誤。舉個例子,如果你在瀏覽器中輸入/Product/apple URL,那麼你將會得到圖1所示的錯誤頁。
圖1:錯誤頁
你實際想做的是只匹配包含合適整數productId的URL。當定義路由來限制與路由相匹配的URL時,你可以使用約束。代碼3中的修改後的Product路由包含了一個Regex,它限制了只匹配數字。
代碼清單3 - Global.asax.cs
routes.MapRoute( "Product", "Product/{productId}", new {controller="Product", action="Details"}, new {productId = @"d+" } );
Regexd+匹配一個或多個整數。這個限制使得Product路由匹配了下面的URL:
但是不匹配下面的URL:
這些瀏覽器請求將由另外的路由處理,或者,如果沒有匹配的路由,將會返回一個“The resource could not be found”錯誤。
4. 建立一個自訂路由約束
這篇教程的目標是示範如何建立一個自訂路由約束。自訂路由約束允許你阻止某個路徑被匹配,除非滿足一些自訂的條件。
在這篇教程中,我們建立了一個Localhost路由約束。Localhost路由約束只匹配本機電腦發出的請求。通過互連網發出的遠程請求不會被匹配。
你可以通過實現IRouteConstraint介面來實現一個自訂路由。這是一個極其簡單的介面,它只描述了一個方法:
bool Match( HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection )
這個方法返回一個布爾值。如果返回了false,與約束相關聯的路由將不會匹配瀏覽器請求。
Localhost約束包含在了代碼清單1中。
代碼清單1 - LocalhostConstraint.cs
using System.Web;
using System.Web.Routing;
namespace MvcApplication1.Constraints {
public class LocalhostConstraint : IRouteConstraint
{
public bool Match
(
HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection
)
{
return httpContext.Request.IsLocal;
}
}
}
代碼清單1中的約束利用了HttpRequest類公布的IsLocal屬性。當發出請求的IP地址是127.0.0.1或者與伺服器的IP地址相同時,這個屬性返回true。
你在定義於Global.asax的路由中使用了自訂約束。代碼清單2中的Global.asax檔案使用了Localhost約束來阻止任何人請求Admin頁面,除非他們從本機伺服器發出請求。舉個例子,當請求來自遠程伺服器時,對於/Admin/DeleteAll的請求將會失敗。
代碼清單2 - Global.asax
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using MvcApplication1.Constraints;
namespace MvcApplication1 {
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes){
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute( "Admin", "Admin/{action}",
new {controller="Admin"},
new {isLocal=new LocalhostConstraint()}
);
//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);
}
}
}
Localhost約束使用在了Admin路由的定義中。這個路由不會被遠程瀏覽器請求所匹配。然而,應該意識到定義在Global.asax中的其他路由可能會匹配相同的請求。理解這一點很重要:約束阻止了特定路由匹配某一請求,而不是所有定義在Global.asax檔案中的路由。
注意到Default路由在代碼清單2中的Glabal.asax檔案中被注釋掉了。如果你包含Default路由,那麼Default路由將會匹配對Admin控制器的請求。在這種情況下,遠端使用者仍然可以調用Admin控制器的動作,即使他們的請求不匹配Admin路由。