ASP.NET 路由
更新:2007 年 11 月
ASP.NET 路由可以讓您使用不需要對應到網站中特定檔案的 URL。因為 URL 不需要對應到任一檔案,所以您可以在 Web 應用程式中使用多個 URL,這些 URL 能夠為使用者的動作提供描述性資訊,因而讓使用者可以更容易地明白。
在沒有使用路由的 ASP.NET 應用程式,任一 URL 的傳入要求通常會對應到硬碟中的實體檔案,例如 .aspx 檔案。例如,https://server/application/Products.aspx?id=4 的要求會對應到 Products.aspx 名為的檔案,檔案中包含用於呈現要回應瀏覽器的程式碼及標記。網頁會使用 id=4 的查詢字串值,判斷要顯示什麼類型的內容,但是這個值對於使用者而已通常不代表任何意義。
您可以在 ASP.NET 路由中定義 URL 規則,其中包含當您處理要求時所使用值的預留位置。在執行階段期間,遵循應用程式名稱之 URL 的各個部分,都會根據已義定的 URL 規則剖析為離散的值。例如,在 https://server/application/Products/show/beverages 的要求中,路由剖析器可以將 Products、show 及 beverages 這幾個值傳入給要求的處理常式。相反的,在不是經過 URL 路由管理要求中,/Products/show/beverages 片段會解譯為應用程式中的檔案路徑。
您也可以使用 URL 規則,以程式設計方式建立對應到不同路由的多個 URL。如此可以讓您集中處理建立 ASP.NET 應用程式中超連結的邏輯。
ASP.NET 路由與 URL 重寫的比較
ASP.NET 路由與其他的 URL 重寫方法是不同的。處理傳入要求時,URL 重寫會以實際變更 URL,然後再將要求傳送到網頁。例如,使用 URL 重寫的應用程式會將 URL 從 /Products/Widgets/ 變更為 /Products.aspx?id=4。此外,URL 重寫通常不需要 API,便可根據您的規則建立 URL。在 URL 重寫,如果您變更了一項 URL 規則,必須手動更新包含該原始 URL 的所有超連結。
使用 ASP.NET 路由後,因為路由可以從 URL 擷取值,所以在處理傳入要求時,不會變更 URL。當您必須建立 URL 時,請將參數值傳入會為您 URL 產生的方法。若要變更 URL 規則,可以在某一處進行變更,然後,您在應用程式根據該規則建立的所有連結都會自動使用新的規則。
定義 URL 路由
您所定義的 URL 規則即稱為「路由」(Route)。在路由中,您可以指定對應到從 URL 要求剖析所得之值的預留位置。您也可以指定用於比對 URL 要求的常數值。
在路由中,您會利用成對的大括號 ( { 及 } ) 定義又稱為「URL 參數」(URL Parameter) 的預留位置。在 URL 剖析時,/ 字元會解譯為一個分隔符號。路由定義中不是分隔符號且不是以大括號包住的資訊會視為常數值。從分隔符號之間擷取的值會指派到預留位置。
您可以在兩個分隔符號之間定義多個預留位置,但是必須以常數值區隔。例如,{language}-{country}/{action} 為有效的路由規則。但是,{language}{country}/{action} 則為無效的規則,因為預留位置之間沒有常數或分隔符號。因此,路由無法判斷要在哪一個位置分隔 language 預留位置的值以及 country 預留位置的值。
下表列出有效的路由規則以及符合該規則的 URL 要求範例。
路由定義 |
比對 URL 的範例 |
---|---|
{controller}/{action}/{id} |
/Products/show/beverages |
{table}/Details.aspx |
/Products/Details.aspx |
blog/{action}/{entry} |
/blog/show/123 |
{reporttype}/{year}/{month}/{day} |
/sales/2008/1/5 |
{locale}/{action} |
/en-US/show |
{language}-{country}/{action} |
/en-US/show |
一般而言,您會在處理常式所呼叫 Application_Start 事件 (Global.asax 檔案中) 的方法中加入路由。這個方法可以確定應用程式執行時可以使用這些路由。此外,也可以讓您在進行應用程式單元測試時,直接呼叫該方法。如果要想進行應用程式單元測試時直接呼叫方法,註冊路由的方法必須為靜態 (Visual Basic 中的 Shared),而且必須有一個 RouteCollection 參數。
將路由加入到 RouteTable 類別的靜態 Routes 屬性,便可新增路由。Routes 屬性為會儲存 ASP.NET 應用程式所有路由的 RouteCollection 物件。下列範例是取自 Global.asax 檔案的程式碼,會加入定義 action 與 categoryName 這兩個參數的 Route 物件。
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
RegisterRoutes(RouteTable.Routes)
End Sub
Shared Sub RegisterRoutes(routes As RouteCollection)
Dim urlPattern As String
Dim categoryRoute As Route
urlPattern = "Category/{action}/{categoryName}"
categoryRoute = New Route(urlPattern, New CategoryRouteHandler)
routes.Add(categoryRoute)
End Sub
protected void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route
(
"Category/{action}/{categoryName}"
, new CategoryRouteHandler()
));
}
指定路由參數的預設值
定義路由時,可以指派參數的預設值。如果該參數的值沒有包含在 URL 中,便會使用預設值。將字典指派給 Route 類別的 Defaults 屬性,即可設定路由的預設值。下列程式碼示範的是具有預設值的路由。
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
RegisterRoutes(RouteTable.Routes)
End Sub
Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
Dim urlPattern As String
Dim categoryRoute As Route
urlPattern = "Category/{action}/{categoryName}"
categoryRoute = New Route(urlPattern, New CategoryRouteHandler)
categoryRoute.Defaults = New RouteValueDictionary(New With _
{.categoryName = "food", _
.action = "show"} )
routes.Add(categoryRoute)
End Sub
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route
(
"Category/{action}/{categoryName}"
new CategoryRouteHandler()
)
{
Defaults = new RouteValueDictionary
{{"categoryName", "food"}, {"action", "show"}}
}
);
}
ASP.NET 路由處理 URL 要求時,範例中顯示的路由定義 (categoryName 的 food 以及 action 的 show 的預設值) 會產生如下表列出的結果:
URL |
參數值 |
---|---|
/Category |
action = "show" (預設值) categoryName = "food" (預設值) |
/Category/add |
action = "add" categoryName = "food" (預設值) |
/Category/add/beverages |
action = "add" categoryName= "beverages" |
處理區段的變數數量。
有時候,您必須處理包含 URL 區段之變數數量的 URL 要求。定義路由時,可以利用星號 (*) 標記參數,藉此指定最後一個參數必須符合 URL 的其他部分。這就是所謂的「catch-all」參數。具有 catch-all 參數的路由,也會比對不包含最後一個參數中任何值的 URL。下列範例示範的是會比對區段中未知數量的路由規則。
query/{queryname}/{*queryvalues}
ASP.NET 路由處理 URL 要求時,範例中顯示的路由定義會產生如下表列出的結果:
URL |
參數值 |
---|---|
/query/select/bikes/onsale |
queryname = "select" queryvalues = "bikes/onsale" |
/query/select/bikes |
queryname = "select" queryvalues = "bikes" |
/query/select |
queryname = "select" queryvalues = 空字串 |
將條件約束加入到路由
除了根據 URL 中的參數數量將 URL 要求與路由定義進行比對以外,您可以指定參數中的值要符合特定條件約束。如果某一 URL 包含的值不屬於路由的條件約束,那麼該路由便不會用於處理要求。您可以加入條件約束,確保 URL 參數包含可以在應用程式中正常運作的值。
使用規則運算式或使用實作 IRouteConstraint 介面的物件,即可定義條件約束。將路由定義加入到 Routes 集合時,可以建立包含驗證測試之 RouteValueDictionary 物件的條件約束。接著,您可以將這個物件指派給 Constraints 屬性。字典中的索引鍵會識別要套用條件約束的參數。字典中的值可以為代表規則運算式的字串或是實作 IRouteConstraint 介面的物件。
如果提供的是字串,路由會將字串視為規則運算式,並且呼叫 Regex 類別的 IsMatch 方法,藉此檢查參數值是否有效。規則運算式一定會區分大小寫。如需詳細資訊,請參閱 .NET Framework 規則運算式。
如果提供的是 IRouteConstraint 物件,ASP.NET 路由會呼叫 IRouteConstraint 物件的 Match 方法,藉此檢查參數值是否有效。Match 會傳回布林值,指出參數值是否有效。
下列程式碼中示範的條件約束,會限制哪些值才可以包含在 locale 與 year 參數中。
Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
Dim urlPattern As String
Dim reportRoute As Route
urlPattern = "{locale}/{year}"
reportRoute = New Route(urlPattern, New ReportRouteHandler)
reportRoute.Constraints = New RouteValueDictionary(New With _
{.locale = "[a-z]{2}-[a-z]{2}", .year = "\d{4}"})
routes.Add(reportRoute)
End Sub
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route
(
"{locale}/{year}"
, new ReportRouteHandler()
)
{
Constraints = new RouteValueDictionary
{{"locale", "[a-z]{2}-[a-z]{2}"},{year, @"\d{4}"}}
});
}
路由處理 URL 要求時,前一個範例中顯示的路由定義會產生如下表列出的結果:
URL |
結果 |
---|---|
/en-US |
沒有符合的結果。同時需要 locale 與 year。 |
/en-US/08 |
沒有符合的結果。year 上的條件約束需要 4 位數。 |
/en-US/2008 |
locale = "en-US" year = "2008" |
不適用路由時的案例
根據預設,路由不會處理對應到 Web 伺服器上現有實體檔案的要求。例如,如果 Products/Beverages/Coffee.aspx 中有實體檔案,則路由不會處理 https://server/application/Products/Beverages/Coffee.aspx 的要求。即使要求符合定義的規則,例如 {controller}/{action}/{id},路由也不會處理讓要求。
如果您希望路由能夠處理所有要求,甚至包含指向檔案的要求,您可以將 RouteCollection 物件的 RouteExistingFiles 屬性設定為 true,覆寫預設行為。將這個值設定為 true 時,路由便會處理所有符合已定義規則的要求。
您也可以指定路由不要處理特定的 URL 要求。定義路由並且指定 StopRoutingHandler 類別不應該用於處理該規則時,可以禁止路由處理特定要求。要求由 StopRoutingHandler 物件處理時,StopRoutingHandler 物件會封鎖以路由的形式對要求進行任何其他的處理。而會以 ASP.NET 網頁、Web 服務或其他 ASP.NET 端點的形式,處理要求。例如,您可以加入下列路由定義,禁止路由處理 WebResource.axd 檔案的要求。
Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
routes.Add(New Route("{resource}.axd/{*pathInfo}", New StopRouteHandler()))
End Sub
public static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route("{resource}.axd/{*pathInfo}", new StopRouteHandler()));
}
URL 如何對應到路由
路由處理 URL 要求時,會嘗試將要求的 URL 與路由進行比對。將 URL 要求與路由進行比對時,需視下列所有情況而異:
路由規則,專案類型中包含已經定義或預設 (如果有) 的路由規則
將規則加入到 Routes 集合的順序。
任何已經提供給路由的預設值。
任何已經提供給路由的條件約束。
是否已經定義要處理符合實體檔案之要求的跟由。
若要避免在處理要求時使用不正確的處理常式,在定義路由時,必須考慮這些所有的條件。Route 物件出現在 Routes 集合中的順序,是很重要的。路由的比對嘗試會從集合中的第一個路由進行到的最後一個路由。出現一個符合結果時,便不會再評估其他路由。通常,將路由加入到 Routes 屬性的建議順序為,從特定性最強的路由定義到最不具特定性的定義。
例如,假設您要加入具有下列規則的路由:
路由 1:{controller}/{action}/{id}
路由 2:products/show/{id}
因為會先評估路由 1,所以路由 2 都不會處理到要求,而且路由 1 一定會比對也適用於路由 2 的要求。https://server/application/products/show/bikes 的要求似乎比較合路由 2,但是會由具備下列值的路由 1 處理這個要求:
controller = products
action = show
id = bikes
如果要求中遺失參數,則會使用預設值。因此,這些預設值可以讓路由比對並非您預期的要求。例如,假設您要加入具有下列規則的路由:
路由 1:{report}/{year}/{month},具有 year 和 month 的預設值。
路由 2:{report}/{year},具有 year 的預設值。
路由 2 永遠也不會處理到要求。路由 1 可能是為月報表所設計,而路由 2 則是為年度報表所設計的。但是,路由 1 預設值的目的為比對任何可能也適用於路由 2 的要求。
您可以加入如 annual/{report}/{year} 與 monthly/{report}/{year}/{month} 的常數,避免規則中發生模稜兩可 (Ambiguity) 的情況。
如果某一個 URL 不符合 RouteTable 集合中定義的任一 Route 物件,則 ASP.NET 路由不會處理該要求。而會以傳入 ASP.NET 網頁、Web 服務或其他 ASP.NET 端點的形式,進行處理。
從路由建立 URL
在您想要集中處理建構 URL 的邏輯時,可以利用路由產生 URL。您可以將參數值以字典的形式,傳入 RouteCollection 物件的 GetVirtualPath 方法,藉此建立 URL。GetVirtualPath 方法會尋找 RouteCollection 物件中第一個符合字典中參數的路由。符合的路由會用於產生 URL。下列範例示範路由定義。
Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
routes.Add(New Route( _
"Category/{action}/{categoryName}", _
New RouteValueDictionary(New With _
{.categoryName = "food", _
.action = "show"}), _
New CategoryRouteHandler()) )
End Sub
public static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route
(
"Category/{action}/{categoryName}"
new CategoryRouteHandler()
)
{
Defaults = new RouteValueDictionary {{"categoryName", "food"},
{"action", "show"}}
}
);
}
下列範例會示範根據路由建立 URL 的控制項。
Dim urlParameters As RouteValueDictionary
urlParameters = New RouteValueDictionary(New With {.categoryName = "beverages", _
.action = "summarize"})
HyperLink1.href = RouteTable.Routes.GetVirtualPath _
(context, urlParameters).VirtualPath
HyperLink1.href = RouteTable.Routes.GetVirtualPath
(context,
new RouteValueDictionary {
{ "categoryName", "beverages" },
{"action", "summarize" }}
).VirtualPath;
這段程式碼執行時,HyperLink1 控制項會在 href 屬性中加入 "Category/summarize/beverages" 這個值。
當您要從路由建立 URL 時,可以加入路由的名稱,從而指定要使用哪一個路由。如需詳細資訊,請參閱 HOW TO:從路由建構 URL。