ASP.NET Core 中的路由

2019-04-17 08:59 更新

路由負(fù)責(zé)將請(qǐng)求 URI 映射到終結(jié)點(diǎn)選擇器并向終結(jié)點(diǎn)調(diào)度傳入的請(qǐng)求。 路由在應(yīng)用中定義,并在應(yīng)用啟動(dòng)時(shí)進(jìn)行配置。 路由可以選擇從請(qǐng)求包含的 URL 中提取值,然后這些值便可用于處理請(qǐng)求。 通過(guò)使用應(yīng)用中的路由信息,路由還能生成映射到終結(jié)點(diǎn)選擇器的 URL。

要在 ASP.NET Core 2.2 中使用最新路由方案,請(qǐng)?jiān)?nbsp;Startup.ConfigureServices 中為 MVC 服務(wù)注冊(cè)指定兼容性版本

C#

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

EnableEndpointRouting 選項(xiàng)確定路由是應(yīng)在內(nèi)部使用 ASP.NET Core 2.1 或更早版本的基于終結(jié)點(diǎn)的邏輯還是使用其基于 IRouter 的邏輯。 兼容性版本設(shè)置為 2.2 或更高版本時(shí),默認(rèn)值為 true。 將值設(shè)置為 false 以使用先前的路由邏輯:

C#

// Use the routing logic of ASP.NET Core 2.1 or earlier:
services.AddMvc(options => options.EnableEndpointRouting = false)
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

有關(guān)基于 IRouter 的路由的詳細(xì)信息,請(qǐng)參閱本主題的 ASP.NET Core 2.1 版本。

 重要

本文檔介紹較低級(jí)別的 ASP.NET Core 路由。 有關(guān) ASP.NET Core MVC 路由的信息,請(qǐng)參閱 在 ASP.NET Core 中路由到控制器操作。 有關(guān) Razor Pages 中路由約定的信息,請(qǐng)參閱 ASP.NET Core 中 Razor 頁(yè)面的路由和應(yīng)用約定。

查看或下載示例代碼如何下載

路由基礎(chǔ)知識(shí)

大多數(shù)應(yīng)用應(yīng)選擇基本的描述性路由方案,讓 URL 有可讀性和意義。 默認(rèn)傳統(tǒng)路由 {controller=Home}/{action=Index}/{id?}:

  • 支持基本的描述性路由方案。
  • 是基于 UI 的應(yīng)用的有用起點(diǎn)。

開發(fā)人員通常在專業(yè)情況下(例如,博客和電子商務(wù)終結(jié)點(diǎn))使用屬性路由或?qū)S脗鹘y(tǒng)路由向應(yīng)用的高流量區(qū)域添加其他簡(jiǎn)潔路由。

Web API 應(yīng)使用屬性路由,將應(yīng)用功能建模為一組資源,其中操作是由 HTTP 謂詞表示。 也就是說(shuō),對(duì)同一邏輯資源執(zhí)行的許多操作(例如,GET、POST)都將使用相同 URL。 屬性路由提供了精心設(shè)計(jì) API 的公共終結(jié)點(diǎn)布局所需的控制級(jí)別。

Razor Pages 應(yīng)用使用默認(rèn)的傳統(tǒng)路由從應(yīng)用的“頁(yè)面”文件夾中提供命名資源。 還可以使用其他約定來(lái)自定義 Razor Pages 路由行為。 有關(guān)詳細(xì)信息,請(qǐng)參閱 ASP.NET Core 中的 Razor 頁(yè)面介紹 和 ASP.NET Core 中 Razor 頁(yè)面的路由和應(yīng)用約定

借助 URL 生成支持,無(wú)需通過(guò)硬編碼 URL 將應(yīng)用關(guān)聯(lián)到一起,即可開發(fā)應(yīng)用。 此支持允許從基本路由配置入手,并在確定應(yīng)用的資源布局后修改路由。

路由使用“終結(jié)點(diǎn)”(Endpoint) 來(lái)表示應(yīng)用中的邏輯終結(jié)點(diǎn)。

終結(jié)點(diǎn)定義用于處理請(qǐng)求的委托和任意元數(shù)據(jù)的集合。 元數(shù)據(jù)用于實(shí)現(xiàn)橫切關(guān)注點(diǎn),該實(shí)現(xiàn)基于附加到每個(gè)終結(jié)點(diǎn)的策略和配置。

路由系統(tǒng)具有以下特征:

  • 路由模板語(yǔ)法用于通過(guò)標(biāo)記化路由參數(shù)來(lái)定義路由。
  • 可以使用常規(guī)樣式和屬性樣式終結(jié)點(diǎn)配置。
  • IRouteConstraint 用于確定 URL 參數(shù)是否包含給定的終結(jié)點(diǎn)約束的有效值。
  • 應(yīng)用模型(如 MVC/Razor Pages)注冊(cè)其所有終結(jié)點(diǎn),這些終結(jié)點(diǎn)具有可預(yù)測(cè)的路由方案實(shí)現(xiàn)。
  • 路由實(shí)現(xiàn)會(huì)在中間件管道中任何所需位置制定路由決策。
  • 路由中間件之后出現(xiàn)的中間件可以檢查路由中間件針對(duì)給定請(qǐng)求 URI 的終結(jié)點(diǎn)決策結(jié)果。
  • 可以在中間件管道中的任何位置枚舉應(yīng)用中的所有終結(jié)點(diǎn)。
  • 應(yīng)用可根據(jù)終結(jié)點(diǎn)信息使用路由生成 URL(例如,用于重定向或鏈接),從而避免硬編碼 URL,這有助于可維護(hù)性。
  • URL 生成是基于支持任意可擴(kuò)展性的地址:可以使用依賴關(guān)系注入 (DI) 在任意位置解析鏈接生成器 API (LinkGenerator) 以生成 URL。如果無(wú)法通過(guò) DI 獲得鏈接生成器 API,則 IUrlHelper 會(huì)提供生成 URL 的方法。

 備注

在 ASP.NET Core 2.2 中發(fā)布終結(jié)點(diǎn)路由后,終結(jié)點(diǎn)鏈接的適用范圍限制為 MVC/Razor Pages 操作和頁(yè)面。 將計(jì)劃在未來(lái)發(fā)布的版本中擴(kuò)展終結(jié)點(diǎn)鏈接的功能。

路由通過(guò) RouterMiddleware 類連接到中間件管道。 ASP.NET Core MVC 向中間件管道添加路由,作為其配置的一部分,并處理 MVC 和 Razor Pages 應(yīng)用中的路由。 要了解如何將路由用作獨(dú)立組件,請(qǐng)參閱使用路由中間件部分。

URL 匹配

URL 匹配是路由向終結(jié)點(diǎn)調(diào)度傳入請(qǐng)求的過(guò)程。 此過(guò)程基于 URL 路徑中的數(shù)據(jù),但可以進(jìn)行擴(kuò)展以考慮請(qǐng)求中的任何數(shù)據(jù)。 向單獨(dú)的處理程序調(diào)度請(qǐng)求的功能是縮放應(yīng)用的大小和復(fù)雜性的關(guān)鍵。

終結(jié)點(diǎn)路由中的路由系統(tǒng)負(fù)責(zé)所有的調(diào)度決策。 由于中間件是基于所選終結(jié)點(diǎn)來(lái)應(yīng)用策略,因此任何可能影響調(diào)度或安全策略應(yīng)用的決策都應(yīng)在路由系統(tǒng)內(nèi)部制定,這一點(diǎn)很重要。

執(zhí)行終結(jié)點(diǎn)委托時(shí),將根據(jù)迄今執(zhí)行的請(qǐng)求處理將 RouteContext.RouteData 的屬性設(shè)為適當(dāng)?shù)闹怠?/p>

RouteData.Values 是從路由中生成的路由值的字典。 這些值通常通過(guò)標(biāo)記 URL 來(lái)確定,可用來(lái)接受用戶輸入,或者在應(yīng)用內(nèi)作出進(jìn)一步的調(diào)度決策。

RouteData.DataTokens 是一個(gè)與匹配的路由相關(guān)的其他數(shù)據(jù)的屬性包。 提供 DataTokens 以支持將狀態(tài)數(shù)據(jù)與每個(gè)路由相關(guān)聯(lián),以便應(yīng)用可根據(jù)所匹配的路由作出決策。 這些值是開發(fā)者定義的,不會(huì)影響通過(guò)任何方式路由的行為。 此外,存儲(chǔ)于 RouteData.DataTokens 中的值可以屬于任何類型,與 RouteData.Values 相反,后者必須能夠轉(zhuǎn)換為字符串,或從字符串進(jìn)行轉(zhuǎn)換。

RouteData.Routers 是參與成功匹配請(qǐng)求的路由的列表。 路由可以相互嵌套。 Routers 屬性可以通過(guò)導(dǎo)致匹配的邏輯路由樹反映該路徑。 通常情況下,Routers 中的第一項(xiàng)是路由集合,應(yīng)該用于生成 URL。 Routers 中的最后一項(xiàng)是匹配的路由處理程序。

URL 生成

URL 生成是通過(guò)其可根據(jù)一組路由值創(chuàng)建 URL 路徑的過(guò)程。 這需要考慮終結(jié)點(diǎn)與訪問(wèn)它們的 URL 之間的邏輯分隔。

終結(jié)點(diǎn)路由包含鏈接生成器 API (LinkGenerator)。 LinkGenerator 是可從 DI 檢索的單一實(shí)例服務(wù)。該 API 可在執(zhí)行請(qǐng)求的上下文之外使用。 MVC 的 IUrlHelper 和依賴 IUrlHelper 的方案(如 Tag Helpers、HTML Helpers 和 Action Results)使用鏈接生成器提供鏈接生成功能。

鏈接生成器基于“地址”和“地址方案”概念。 地址方案是確定哪些終結(jié)點(diǎn)用于鏈接生成的方式。 例如,許多用戶熟悉的來(lái)自 MVC/Razor Pages 的路由名稱和路由值方案都是作為地址方案實(shí)現(xiàn)的。

鏈接生成器可以通過(guò)以下擴(kuò)展方法鏈接到 MVC/Razor Pages 操作和頁(yè)面:

這些方法的重載接受包含 HttpContext 的參數(shù)。 這些方法在功能上等同于 Url.Action 和 Url.Page,但提供了更大的靈活性和更多的選項(xiàng)。

GetPath* 方法與 Url.Action 和 Url.Page 最相似,因?yàn)樗鼈兩砂^對(duì)路徑的 URI。 GetUri*方法始終生成包含方案和主機(jī)的絕對(duì) URI。 接受 HttpContext 的方法在執(zhí)行請(qǐng)求的上下文中生成 URI。 除非重寫,否則將使用來(lái)自執(zhí)行請(qǐng)求的環(huán)境路由值、URL 基本路徑、方案和主機(jī)。

使用地址調(diào)用 LinkGenerator。 生成 URI 的過(guò)程分兩步進(jìn)行:

  1. 將地址綁定到與地址匹配的終結(jié)點(diǎn)列表。
  2. 計(jì)算每個(gè)終結(jié)點(diǎn)的 RoutePattern,直到找到與提供的值匹配的路由模式。 輸出結(jié)果會(huì)與提供給鏈接生成器的其他 URI 部分進(jìn)行組合并返回。

對(duì)任何類型的地址,LinkGenerator 提供的方法均支持標(biāo)準(zhǔn)鏈接生成功能。 使用鏈接生成器的最簡(jiǎn)便方法是通過(guò)擴(kuò)展方法對(duì)特定地址類型執(zhí)行操作。

擴(kuò)展方法說(shuō)明
GetPathByAddress根據(jù)提供的值生成具有絕對(duì)路徑的 URI。
GetUriByAddress根據(jù)提供的值生成絕對(duì) URI。

 警告

請(qǐng)注意有關(guān)調(diào)用 LinkGenerator 方法的下列含義:

  • 對(duì)于不驗(yàn)證傳入請(qǐng)求的 Host 標(biāo)頭的應(yīng)用配置,請(qǐng)謹(jǐn)慎使用 GetUri* 擴(kuò)展方法。 如果未驗(yàn)證傳入請(qǐng)求的 Host標(biāo)頭,則可能以視圖/頁(yè)面中 URI 的形式將不受信任的請(qǐng)求輸入發(fā)送回客戶端。 建議所有生產(chǎn)應(yīng)用都將其服務(wù)器配置為針對(duì)已知有效值驗(yàn)證 Host 標(biāo)頭。
  • 在中間件中將 LinkGenerator 與 Map 或 MapWhen 結(jié)合使用時(shí),請(qǐng)小心謹(jǐn)慎。 Map* 會(huì)更改執(zhí)行請(qǐng)求的基路徑,這會(huì)影響鏈接生成的輸出。 所有 LinkGenerator API 都允許指定基路徑。 始終指定一個(gè)空的基路徑來(lái)撤消 Map* 對(duì)鏈接生成的影響。

與早期版本路由的差異

ASP.NET Core 2.2 或更高版本中的終結(jié)點(diǎn)路由與 ASP.NET Core 中早期版本的路由之間存在一些差異:

  • 終結(jié)點(diǎn)路由系統(tǒng)不支持基于 IRouter 的可擴(kuò)展性,包括從 Route 繼承。
  • 終結(jié)點(diǎn)路由不支持 WebApiCompatShim。 使用 2.1 兼容性版本 (.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)) 以繼續(xù)使用兼容性填充碼。
  • 在使用傳統(tǒng)路由時(shí),終結(jié)點(diǎn)路由對(duì)生成的 URI 的大小寫具有不同的行為。請(qǐng)考慮以下默認(rèn)路由模板:C#復(fù)制app.UseMvc(routes => { routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"); }); 假設(shè)使用以下路由生成某個(gè)操作的鏈接:C#復(fù)制var link = Url.Action("ReadPost", "blog", new { id = 17, }); 如果使用基于 IRouter 的路由,此代碼生成 URI /blog/ReadPost/17,該 URI 遵循所提供路由值的大小寫。 ASP.NET Core 2.2 或更高版本中的終結(jié)點(diǎn)路由生成 /Blog/ReadPost/17(“Blog”大寫)。 終結(jié)點(diǎn)路由提供 IOutboundParameterTransformer 接口,可用于在全局范圍自定義此行為或應(yīng)用不同的約定來(lái)映射 URL。有關(guān)詳細(xì)信息,請(qǐng)參閱參數(shù)轉(zhuǎn)換器參考部分。
  • 在試圖鏈接到不存在的控制器/操作或頁(yè)面時(shí),MVC/Razor Pages 通過(guò)傳統(tǒng)路由執(zhí)行的鏈接生成,其操作具有不同的行為。請(qǐng)考慮以下默認(rèn)路由模板:C#復(fù)制app.UseMvc(routes => { routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"); }); 假設(shè)使用以下路由通過(guò)默認(rèn)模板生成某個(gè)操作的鏈接:C#復(fù)制var link = Url.Action("ReadPost", "Blog", new { id = 17, }); 如果使用基于 IRouter 的路由,即使 BlogController 不存在或沒(méi)有 ReadPost 操作方法,結(jié)果也始終為 /Blog/ReadPost/17。 正如所料,如果操作方法存在,ASP.NET Core 2.2 或更高版本中的終結(jié)點(diǎn)路由會(huì)生成 /Blog/ReadPost/17。 但是,如果操作不存在,終結(jié)點(diǎn)路由會(huì)生成空字符串。 從概念上講,如果操作不存在,終結(jié)點(diǎn)路由不會(huì)假定終結(jié)點(diǎn)存在。
  • 與終結(jié)點(diǎn)路由一起使用時(shí),鏈接生成環(huán)境值失效算法的行為會(huì)有所不同。環(huán)境值失效是一種算法,用于決定當(dāng)前正在執(zhí)行的請(qǐng)求(環(huán)境值)中的哪些路由值可用于鏈接生成操作。 鏈接到不同操作時(shí),傳統(tǒng)路由會(huì)使額外的路由值失效。 ASP.NET Core 2.2 之前的版本中,屬性路由不具有此行為。 在 ASP.NET Core 的早期版本中,如果有另一個(gè)操作使用同一路由參數(shù)名稱,則該操作的鏈接會(huì)導(dǎo)致發(fā)生鏈接生成錯(cuò)誤。 在 ASP.NET Core 2.2 或更高版本中,鏈接到另一個(gè)操作時(shí),這兩種路由形式都會(huì)使值失效。請(qǐng)考慮 ASP.NET Core2.1 或更高版本中的以下示例。 鏈接到另一個(gè)操作(或另一頁(yè)面)時(shí),路由值可能會(huì)按非預(yù)期的方式被重用。在 /Pages/Store/Product.cshtml 中:CSHTML復(fù)制@page "{id}" @Url.Page("/Login") 在 /Pages/Login.cshtml 中:CSHTML復(fù)制@page "{id?}" 如果 ASP.NET Core 2.1 或更早版本中的 URI 為 /Store/Product/18,則由 @Url.Page("/Login")在 Store/Info 頁(yè)面上生成的鏈接為 /Login/18。 即使鏈接目標(biāo)完全是應(yīng)用的其他部分,仍會(huì)重用 id 值 18。 /Login 頁(yè)面上下文中的 id 路由值可能是用戶 ID 值,而非存儲(chǔ)產(chǎn)品 ID 值。在 ASP.NET Core 2.2 或更高版本的終結(jié)點(diǎn)路由中,結(jié)果為 /Login。 當(dāng)鏈接的目標(biāo)是另一個(gè)操作或頁(yè)面時(shí),不會(huì)重復(fù)使用環(huán)境值。
  • 往返路由參數(shù)語(yǔ)法:使用雙星號(hào) (**) catch-all 參數(shù)語(yǔ)法時(shí),不對(duì)正斜杠進(jìn)行編碼。在鏈接生成期間,路由系統(tǒng)對(duì)雙星號(hào) (**) catch-all 參數(shù)(例如,{**myparametername})中捕獲的除正斜杠外的值進(jìn)行編碼。 在 ASP.NET Core 2.2 或更高版本中,基于 IRouter 的路由支持雙星號(hào) catch-all 參數(shù)。ASP.NET Core ({*myparametername}) 早期版本中的單星號(hào) catch-all 參數(shù)語(yǔ)法仍然受支持,并對(duì)正斜杠進(jìn)行編碼。路由鏈接生成方式為Url.Action(new { category = "admin/products" })…/search/{*page}/search/admin%2Fproducts(對(duì)正斜杠進(jìn)行編碼)/search/{**page}/search/admin/products

中間件示例

在以下示例中,中間件使用 LinkGenerator API 創(chuàng)建列出存儲(chǔ)產(chǎn)品的操作方法的鏈接。 應(yīng)用中的任何類都可通過(guò)將鏈接生成器注入類并調(diào)用 GenerateLink 來(lái)使用鏈接生成器。

C#

using Microsoft.AspNetCore.Routing;

public class ProductsLinkMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsLinkMiddleware(RequestDelegate next, LinkGenerator linkGenerator)
    {
        _linkGenerator = linkGenerator;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        var url = _linkGenerator.GetPathByAction("ListProducts", "Store");

        httpContext.Response.ContentType = "text/plain";

        await httpContext.Response.WriteAsync($"Go to {url} to see our products.");
    }
}

創(chuàng)建路由

大多數(shù)應(yīng)用通過(guò)調(diào)用 MapRoute 或 IRouteBuilder 上定義的一種類似的擴(kuò)展方法來(lái)創(chuàng)建路由。 任何 IRouteBuilder 擴(kuò)展方法都會(huì)創(chuàng)建 Route 的實(shí)例并將其添加到路由集合中。

MapRoute 不接受路由處理程序參數(shù)。 MapRoute 僅添加由 DefaultHandler 處理的路由。 要了解 MVC 中的路由的詳細(xì)信息,請(qǐng)參閱 在 ASP.NET Core 中路由到控制器操作。

以下代碼示例是典型 ASP.NET Core MVC 路由定義使用的一個(gè) MapRoute 調(diào)用示例:

C#

routes.MapRoute(
    name: "default",
    template: "{controller=Home}/{action=Index}/{id?}");

此模板與 URL 路徑相匹配,并且提取路由值。 例如,路徑 /Products/Details/17 生成以下路由值:{ controller = Products, action = Details, id = 17 }。

路由值是通過(guò)將 URL 路徑拆分成段,并且將每段與路由模板中的路由參數(shù)名稱相匹配來(lái)確定的。 路由參數(shù)已命名。 參數(shù)通過(guò)將參數(shù)名稱括在大括號(hào) { ... } 中來(lái)定義。

上述模板還可匹配 URL 路徑 /,并且生成值 { controller = Home, action = Index }。 這是因?yàn)?nbsp;{controller} 和 {action} 路由參數(shù)具有默認(rèn)值,且 id 路由參數(shù)是可選的。 路由參數(shù)名稱為參數(shù)定義默認(rèn)值后,等號(hào) (=) 后將有一個(gè)值。 路由參數(shù)名稱后面的問(wèn)號(hào) (?) 定義了可選參數(shù)。

路由匹配時(shí),具有默認(rèn)值的路由參數(shù)始終會(huì)生成路由值。 如果沒(méi)有相應(yīng)的 URL 路徑段,則可選參數(shù)不會(huì)生成路由值。 有關(guān)路由模板方案和語(yǔ)法的詳細(xì)說(shuō)明,請(qǐng)參閱路由模板參考部分。

在以下示例中,路由參數(shù)定義 {id:int} 為 id 路由參數(shù)定義路由約束

C#

routes.MapRoute(
    name: "default",
    template: "{controller=Home}/{action=Index}/{id:int}");

此模板與類似于 /Products/Details/17 而不是 /Products/Details/Apples 的 URL 路徑相匹配。 路由約束實(shí)現(xiàn) IRouteConstraint 并檢查路由值,以驗(yàn)證它們。 在此示例中,路由值 id 必須可轉(zhuǎn)換為整數(shù)。 有關(guān)框架提供的路由約束的說(shuō)明,請(qǐng)參閱路由約束參考。

其他 MapRoute 重載接受 constraints、dataTokens 和 defaults 的值。 這些參數(shù)的典型用法是傳遞匿名類型化對(duì)象,其中匿名類型的屬性名匹配路由參數(shù)名稱。

以下 MapRoute 示例可創(chuàng)建等效路由:

C#

routes.MapRoute(
    name: "default_route",
    template: "{controller}/{action}/{id?}",
    defaults: new { controller = "Home", action = "Index" });

routes.MapRoute(
    name: "default_route",
    template: "{controller=Home}/{action=Index}/{id?}");

 提示

定義約束和默認(rèn)值的內(nèi)聯(lián)語(yǔ)法對(duì)于簡(jiǎn)單路由很方便。 但是,存在內(nèi)聯(lián)語(yǔ)法不支持的方案,例如數(shù)據(jù)令牌。

以下示例演示了一些其他方案:

C#

routes.MapRoute(
    name: "blog",
    template: "Blog/{**article}",
    defaults: new { controller = "Blog", action = "ReadArticle" });

上述模板與 /Blog/All-About-Routing/Introduction 等的 URL 路徑相匹配,并提取值 { controller = Blog, action = ReadArticle, article = All-About-Routing/Introduction }。 controller 和 action 的默認(rèn)路由值由路由生成,即便模板中沒(méi)有對(duì)應(yīng)的路由參數(shù)。 可在路由模板中指定默認(rèn)值。 根據(jù)路由參數(shù)名稱前的雙星號(hào) (**) 外觀,article 路由參數(shù)被定義為 catch-all。全方位路由參數(shù)可捕獲 URL 路徑的其余部分,也能匹配空白字符串。

以下示例添加了路由約束和數(shù)據(jù)令牌:

C#

routes.MapRoute(
    name: "us_english_products",
    template: "en-US/Products/{id}",
    defaults: new { controller = "Products", action = "Details" },
    constraints: new { id = new IntRouteConstraint() },
    dataTokens: new { locale = "en-US" });

上述模板與 /en-US/Products/5 等 URL 路徑相匹配,并且提取值 { controller = Products, action = Details, id = 5 } 和數(shù)據(jù)令牌 { locale = en-US }。

本地 Windows 令牌

路由類 URL 生成

Route 類還可通過(guò)將一組路由值與其路由模板組合來(lái)生成 URL。 從邏輯上來(lái)說(shuō),這是匹配 URL 路徑的反向過(guò)程。

 提示

為更好了解 URL 生成,請(qǐng)想象你要生成的 URL,然后考慮路由模板將如何匹配該 URL。 將生成什么值? 這大致相當(dāng)于 URL 在 Route 類中的生成方式。

以下示例使用常規(guī) ASP.NET Core MVC 默認(rèn)路由:

C#

routes.MapRoute(
    name: "default",
    template: "{controller=Home}/{action=Index}/{id?}");

使用路由值 { controller = Products, action = List },將生成 URL /Products/List。 路由值將替換為相應(yīng)的路由參數(shù),以形成 URL 路徑。 由于 id 是可選路由參數(shù),因此成功生成的 URL 不具有 id 的值。

使用路由值 { controller = Home, action = Index },將生成 URL /。 提供的路由值與默認(rèn)值匹配,并且會(huì)安全地省略與默認(rèn)值對(duì)應(yīng)的段。

已生成的兩個(gè) URL 將往返以下路由定義 (/Home/Index 和 /),并生成用于生成該 URL 的相同路由值。

 備注

使用 ASP.NET Core MVC 應(yīng)用應(yīng)該使用 UrlHelper 生成 URL,而不是直接調(diào)用到路由。

有關(guān) URL 生成的詳細(xì)信息,請(qǐng)參閱 Url 生成參考部分。

使用路由中間件

引用應(yīng)用項(xiàng)目文件中的 Microsoft.AspNetCore.App 元包。

將路由添加到 Startup.ConfigureServices 中的服務(wù)容器:

C#

public void ConfigureServices(IServiceCollection services)
{
    services.AddRouting();
}

必須在 Startup.Configure 方法中配置路由。 該示例應(yīng)用使用以下 API:

C#

var trackPackageRouteHandler = new RouteHandler(context =>
{
    var routeValues = context.GetRouteData().Values;
    return context.Response.WriteAsync(
        $"Hello! Route values: {string.Join(", ", routeValues)}");
});

var routeBuilder = new RouteBuilder(app, trackPackageRouteHandler);

routeBuilder.MapRoute(
    "Track Package Route",
    "package/{operation:regex(^track|create|detonate$)}/{id:int}");

routeBuilder.MapGet("hello/{name}", context =>
{
    var name = context.GetRouteValue("name");
    // The route handler when HTTP GET "hello/<anything>" matches
    // To match HTTP GET "hello/<anything>/<anything>, 
    // use routeBuilder.MapGet("hello/{*name}"
    return context.Response.WriteAsync($"Hi, {name}!");
});

var routes = routeBuilder.Build();
app.UseRouter(routes);

下表顯示了具有給定 URI 的響應(yīng)。

URI響應(yīng)
/package/create/3Hello! Route values: [operation, create], [id, 3]
/package/track/-3Hello! Route values: [operation, track], [id, -3]
/package/track/-3/Hello! Route values: [operation, track], [id, -3]
/package/track/請(qǐng)求失敗,不匹配。
GET /hello/JoeHi, Joe!
POST /hello/Joe請(qǐng)求失敗,僅匹配 HTTP GET。
GET /hello/Joe/Smith請(qǐng)求失敗,不匹配。

框架可提供一組用于創(chuàng)建路由 (RequestDelegateRouteBuilderExtensions) 的擴(kuò)展方法:

Map[Verb] 方法將使用約束來(lái)將路由限制為方法名稱中的 HTTP 謂詞。 有關(guān)示例,請(qǐng)參閱 MapGet和 MapVerb。

路由模板參考

如果路由找到匹配項(xiàng),大括號(hào) ({ ... }) 內(nèi)的令牌定義綁定的路由參數(shù)。 可在路由段中定義多個(gè)路由參數(shù),但必須由文本值隔開。 例如,{controller=Home}{action=Index} 不是有效的路由,因?yàn)?nbsp;{controller} 和 {action} 之間沒(méi)有文本值。 這些路由參數(shù)必須具有名稱,且可能指定了其他屬性。

路由參數(shù)以外的文本(例如 {id})和路徑分隔符 / 必須匹配 URL 中的文本。 文本匹配區(qū)分大小寫,并且基于 URL 路徑已解碼的表示形式。 要匹配文字路由參數(shù)分隔符({ 或 }),請(qǐng)通過(guò)重復(fù)該字符({{ 或 }})來(lái)轉(zhuǎn)義分隔符。

嘗試捕獲具有可選文件擴(kuò)展名的文件名的 URL 模式還有其他注意事項(xiàng)。 例如,考慮模板 files/{filename}.{ext?}。 當(dāng) filename 和 ext 的值都存在時(shí),將填充這兩個(gè)值。 如果 URL 中僅存在 filename 的值,則路由匹配,因?yàn)槲搽S句點(diǎn) (.) 是可選的。 以下 URL 與此路由相匹配:

  • /files/myFile.txt
  • /files/myFile

可以使用星號(hào) (*) 或雙星號(hào) (**) 作為路由參數(shù)的前綴,以綁定到 URI 的其余部分。 這些稱為 catch-all 參數(shù)。 例如,blog/{**slug} 將匹配以 /blog 開頭且其后帶有任何值(將分配給 slug路由值)的 URI。 全方位參數(shù)還可以匹配空字符串。

使用路由生成 URL(包括路徑分隔符 (/))時(shí),catch-all 參數(shù)會(huì)轉(zhuǎn)義相應(yīng)的字符。 例如,路由值為 { path = "my/path" } 的路由 foo/{*path} 生成 foo/my%2Fpath。 請(qǐng)注意轉(zhuǎn)義的正斜杠。 要往返路徑分隔符,請(qǐng)使用 ** 路由參數(shù)前綴。 { path = "my/path" } 的路由 foo/{**path} 生成 foo/my/path。

路由參數(shù)可能具有指定的默認(rèn)值,方法是在參數(shù)名稱后使用等號(hào) (=) 隔開以指定默認(rèn)值。 例如,{controller=Home} 將 Home 定義為 controller 的默認(rèn)值。 如果參數(shù)的 URL 中不存在任何值,則使用默認(rèn)值。 通過(guò)在參數(shù)名稱的末尾附加問(wèn)號(hào) (?) 可使路由參數(shù)成為可選項(xiàng),如 id? 中所示。 可選值和默認(rèn)路徑參數(shù)的區(qū)別在于具有默認(rèn)值的路由參數(shù)始終會(huì)生成值 —,而可選參數(shù)僅當(dāng)請(qǐng)求 URL 提供值時(shí)才會(huì)具有值。

路由參數(shù)可能具有必須與從 URL 中綁定的路由值匹配的約束。 在路由參數(shù)后面添加一個(gè)冒號(hào) (:) 和約束名稱可指定路由參數(shù)上的內(nèi)聯(lián)約束。 如果約束需要參數(shù),將以在約束名稱后括在括號(hào) ((...)) 中的形式提供。 通過(guò)追加另一個(gè)冒號(hào) (:) 和約束名稱,可指定多個(gè)內(nèi)聯(lián)約束。

約束名稱和參數(shù)將傳遞給 IInlineConstraintResolver 服務(wù),以創(chuàng)建 IRouteConstraint 的實(shí)例,用于處理 URL。 例如,路由模板 blog/{article:minlength(10)} 使用參數(shù) 10 指定 minlength 約束。 有關(guān)路由約束詳情以及框架提供的約束列表,請(qǐng)參閱路由約束引用部分。

路由參數(shù)還可以具有參數(shù)轉(zhuǎn)換器,用于在生成鏈接以及將操作和頁(yè)面匹配到 URI 時(shí)轉(zhuǎn)換參數(shù)的值。與約束類似,可以通過(guò)在路由參數(shù)名稱后面添加冒號(hào) (:) 和轉(zhuǎn)換器名稱,將參數(shù)變換器內(nèi)聯(lián)添加到路徑參數(shù)。 例如,路由模板 blog/{article:slugify} 指定 slugify 轉(zhuǎn)換器。 有關(guān)參數(shù)轉(zhuǎn)換的詳細(xì)信息,請(qǐng)參閱參數(shù)轉(zhuǎn)換器參考部分。

下表演示了示例路由模板及其行為。

路由模板示例匹配 URI請(qǐng)求 URI…
hello/hello僅匹配單個(gè)路徑 /hello。
{Page=Home}/匹配并將 Page 設(shè)置為 Home。
{Page=Home}/Contact匹配并將 Page 設(shè)置為 Contact。
{controller}/{action}/{id?}/Products/List映射到 Products控制器和 List 操作。
{controller}/{action}/{id?}/Products/Details/123映射到 Products控制器和 Details 操作(id 設(shè)置為 123)。
{controller=Home}/{action=Index}/{id?}/映射到 Home 控制器和 Index 方法(忽略 id)。

使用模板通常是進(jìn)行路由最簡(jiǎn)單的方法。 還可在路由模板外指定約束和默認(rèn)值。

 提示

啟用日志記錄以查看內(nèi)置路由實(shí)現(xiàn)(如 Route)如何匹配請(qǐng)求。

保留的路由名稱

以下關(guān)鍵字是保留的名稱,它們不能用作路由名稱或參數(shù):

  • action
  • area
  • controller
  • handler
  • page

路由約束參考

路由約束在傳入 URL 發(fā)生匹配時(shí)執(zhí)行,URL 路徑標(biāo)記為路由值。 路徑約束通常檢查通過(guò)路徑模板關(guān)聯(lián)的路徑值,并對(duì)該值是否可接受做出是/否決定。 某些路由約束使用路由值以外的數(shù)據(jù)來(lái)考慮是否可以路由請(qǐng)求。 例如,HttpMethodRouteConstraint 可以根據(jù)其 HTTP 謂詞接受或拒絕請(qǐng)求。 約束用于路由請(qǐng)求和鏈接生成。

 警告

請(qǐng)勿將約束用于“輸入驗(yàn)證”。 如果將約束用于“輸入約束”,那么無(wú)效輸入將導(dǎo)致“404 - 未找到”響應(yīng),而不是含相應(yīng)錯(cuò)誤消息的“400 - 錯(cuò)誤請(qǐng)求”。 路由約束用于消除類似路由的歧義,而不是驗(yàn)證特定路由的輸入。

下表演示示例路由約束及其預(yù)期行為。

約束示例匹配項(xiàng)示例說(shuō)明
int{id:int}123456789, -123456789匹配任何整數(shù)
bool{active:bool}true, FALSE匹配 true或 false(區(qū)分大小寫)
datetime{dob:datetime}2016-12-31, 2016-12-31 7:32pm匹配有效的 DateTime值(位于固定區(qū)域性中 - 查看警告)
decimal{price:decimal}49.99, -1,000.01匹配有效的 decimal 值(位于固定區(qū)域性中 - 查看警告)
double{weight:double}1.234, -1,001.01e8匹配有效的 double 值(位于固定區(qū)域性中 - 查看警告)
float{weight:float}1.234, -1,001.01e8匹配有效的 float 值(位于固定區(qū)域性中 - 查看警告)
guid{id:guid}CD2C1638-1638-72D5-1638-DEADBEEF1638, {CD2C1638-1638-72D5-1638-DEADBEEF1638}匹配有效的 Guid 值
long{ticks:long}123456789, -123456789匹配有效的 long 值
minlength(value){username:minlength(4)}Rick字符串必須至少為 4 個(gè)字符
maxlength(value){filename:maxlength(8)}Richard字符串不得超過(guò) 8 個(gè)字符
length(length){filename:length(12)}somefile.txt字符串必須正好為 12 個(gè)字符
length(min,max){filename:length(8,16)}somefile.txt字符串必須至少為 8 個(gè)字符,且不得超過(guò) 16 個(gè)字符
min(value){age:min(18)}19整數(shù)值必須至少為 18
max(value){age:max(120)}91整數(shù)值不得超過(guò) 120
range(min,max){age:range(18,120)}91整數(shù)值必須至少為 18,且不得超過(guò) 120
alpha{name:alpha}Rick字符串必須由一個(gè)或多個(gè)字母字符(a-z,區(qū)分大小寫)組成
regex(expression){ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}123-45-6789字符串必須匹配正則表達(dá)式(參見有關(guān)定義正則表達(dá)式的提示)
required{name:required}Rick用于強(qiáng)制在 URL 生成過(guò)程中存在非參數(shù)值

可向單個(gè)參數(shù)應(yīng)用多個(gè)由冒號(hào)分隔的約束。 例如,以下約束將參數(shù)限制為大于或等于 1 的整數(shù)值:

C#

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

 警告

驗(yàn)證 URL 的路由約束并將轉(zhuǎn)換為始終使用固定區(qū)域性的 CLR 類型(例如 int 或 DateTime)。 這些約束假定 URL 不可本地化。 框架提供的路由約束不會(huì)修改存儲(chǔ)于路由值中的值。 從 URL 中分析的所有路由值都將存儲(chǔ)為字符串。 例如,float 約束會(huì)嘗試將路由值轉(zhuǎn)換為浮點(diǎn)數(shù),但轉(zhuǎn)換后的值僅用來(lái)驗(yàn)證其是否可轉(zhuǎn)換為浮點(diǎn)數(shù)。

正則表達(dá)式

ASP.NET Core 框架將向正則表達(dá)式構(gòu)造函數(shù)添加 RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant。 有關(guān)這些成員的說(shuō)明,請(qǐng)參閱 RegexOptions

正則表達(dá)式與路由和 C# 計(jì)算機(jī)語(yǔ)言使用的分隔符和令牌相似。 必須對(duì)正則表達(dá)式令牌進(jìn)行轉(zhuǎn)義。要在路由中使用正則表達(dá)式 ^\d{3}-\d{2}-\d{4}$,表達(dá)式必須在字符串中提供 \(單反斜杠)字符,正如 C# 源文件中的 \\(雙反斜杠)字符一樣,以便對(duì) \ 字符串轉(zhuǎn)義字符進(jìn)行轉(zhuǎn)義(除非使用字符串文本)。 要對(duì)路由參數(shù)分隔符進(jìn)行轉(zhuǎn)義({、}、[、]),請(qǐng)將表達(dá)式({{、}、[[、]])中的字符數(shù)加倍。 下表展示了正則表達(dá)式和轉(zhuǎn)義版本。

正則表達(dá)式轉(zhuǎn)義后的正則表達(dá)式
^\d{3}-\d{2}-\d{4}$^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$^[[a-z]]{{2}}$

路由中使用的正則表達(dá)式通常以脫字號(hào) (^) 開頭,并匹配字符串的起始位置。 表達(dá)式通常以美元符號(hào) ($) 字符結(jié)尾,并匹配字符串的結(jié)尾。 ^ 和 $ 字符可確保正則表達(dá)式匹配整個(gè)路由參數(shù)值。如果沒(méi)有 ^ 和 $ 字符,正則表達(dá)式將匹配字符串內(nèi)的所有子字符串,而這通常是不需要的。 下表提供了示例并說(shuō)明了它們匹配或匹配失敗的原因。

表達(dá)式String匹配注釋
[a-z]{2}hello子字符串匹配
[a-z]{2}123abc456子字符串匹配
[a-z]{2}mz匹配表達(dá)式
[a-z]{2}MZ不區(qū)分大小寫
^[a-z]{2}$helloNo參閱上述 ^ 和 $
^[a-z]{2}$123abc456No參閱上述 ^ 和 $

有關(guān)正則表達(dá)式語(yǔ)法的詳細(xì)信息,請(qǐng)參閱 .NET Framework 正則表達(dá)式。

若要將參數(shù)限制為一組已知的可能值,可使用正則表達(dá)式。 例如,{action:regex(^(list|get|create)$)} 僅將 action 路由值匹配到 list、get 或 create。 如果傳遞到約束字典中,字符串 ^(list|get|create)$ 將等效。 已傳遞到約束字典(不與模板內(nèi)聯(lián))且不匹配任何已知約束的約束還將被視為正則表達(dá)式。

自定義路由約束

除了內(nèi)置路由約束以外,還可以通過(guò)實(shí)現(xiàn) IRouteConstraint 接口來(lái)創(chuàng)建自定義路由約束。IRouteConstraint 接口包含一個(gè)方法 Match,當(dāng)滿足約束時(shí),它返回 true,否則返回 false。

若要使用自定義 IRouteConstraint,必須在應(yīng)用的服務(wù)容器中使用應(yīng)用的 ConstraintMap 注冊(cè)路由約束類型。 ConstraintMap 是將路由約束鍵映射到驗(yàn)證這些約束的 IRouteConstraint 實(shí)現(xiàn)的目錄。 應(yīng)用的 ConstraintMap 可作為 services.AddRouting 調(diào)用的一部分在 Startup.ConfigureServices 中進(jìn)行更新,也可以通過(guò)使用 services.Configure<RouteOptions> 直接配置 RouteOptions 進(jìn)行更新。 例如:

C#

services.AddRouting(options =>
{
    options.ConstraintMap.Add("customName", typeof(MyCustomConstraint));
});

然后,可以使用在注冊(cè)約束類型時(shí)指定的名稱,以常規(guī)方式將約束應(yīng)用于路由。 例如:

C#

[HttpGet("{id:customName}")]
public ActionResult<string> Get(string id)

參數(shù)轉(zhuǎn)換器參考

參數(shù)轉(zhuǎn)換器:

  • 在為 Route 生成鏈接時(shí)執(zhí)行。
  • 實(shí)現(xiàn) Microsoft.AspNetCore.Routing.IOutboundParameterTransformer。
  • 使用 ConstraintMap 進(jìn)行配置。
  • 獲取參數(shù)的路由值并將其轉(zhuǎn)換為新的字符串值。
  • 在生成的鏈接中使用轉(zhuǎn)換后的值的結(jié)果。

例如,路由模式 blog\{article:slugify}(具有 Url.Action(new { article = "MyTestArticle" }))中的自定義 slugify 參數(shù)轉(zhuǎn)換器生成 blog\my-test-article。

若要在路由模式中使用參數(shù)轉(zhuǎn)換器,請(qǐng)先在 Startup.ConfigureServices 中使用 ConstraintMap 對(duì)其進(jìn)行配置:

C#

services.AddRouting(options =>
{
    // Replace the type and the name used to refer to it with your own
    // IOutboundParameterTransformer implementation
    options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});

框架使用參數(shù)轉(zhuǎn)化器來(lái)轉(zhuǎn)換進(jìn)行終結(jié)點(diǎn)解析的 URI。 例如,ASP.NET Core MVC 使用參數(shù)轉(zhuǎn)換器來(lái)轉(zhuǎn)換用于匹配 area``controller``action 和 page 的路由值。

C#

routes.MapRoute(
    name: "default",
    template: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");

使用上述路由,操作 SubscriptionManagementController.GetAll() 與 URI /subscription-management/get-all 匹配。 參數(shù)轉(zhuǎn)換器不會(huì)更改用于生成鏈接的路由值。 例如,Url.Action("GetAll", "SubscriptionManagement") 輸出 /subscription-management/get-all。

對(duì)于結(jié)合使用參數(shù)轉(zhuǎn)換器和所生成的路由,ASP.NET Core 提供了 API 約定:

  • ASP.NET Core MVC 還具有 Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention API 約定。該約定將指定的參數(shù)轉(zhuǎn)換器應(yīng)用于應(yīng)用中的所有屬性路由。 在替換屬性路徑令牌時(shí),參數(shù)轉(zhuǎn)換器將轉(zhuǎn)換這些令牌。 有關(guān)詳細(xì)信息,請(qǐng)參閱使用參數(shù)轉(zhuǎn)換器自定義標(biāo)記替換。
  • Razor Pages 具有 Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteTransformerConvention API 約定。此約定將指定的參數(shù)轉(zhuǎn)換器應(yīng)用于所有自動(dòng)發(fā)現(xiàn)的 Razor Pages。 參數(shù)轉(zhuǎn)換器轉(zhuǎn)換 Razor Pages.路由的文件夾和文件名段。 有關(guān)詳細(xì)信息,請(qǐng)參閱使用參數(shù)轉(zhuǎn)換器自定義頁(yè)面路由

URL 生成參考

以下示例演示如何在給定路由值字典和 RouteCollection 的情況下生成路由鏈接。

C#

app.Run(async (context) =>
{
    var dictionary = new RouteValueDictionary
    {
        { "operation", "create" },
        { "id", 123}
    };

    var vpc = new VirtualPathContext(context, null, dictionary, 
        "Track Package Route");
    var path = routes.GetVirtualPath(vpc).VirtualPath;

    context.Response.ContentType = "text/html";
    await context.Response.WriteAsync("Menu<hr/>");
    await context.Response.WriteAsync(
        $"<a href='{path}'>Create Package 123</a><br/>");
});

上述示例末尾生成的 VirtualPath 為 /package/create/123。 字典提供“跟蹤包路由”模板 package/{operation}/{id} 的 operation 和 id 路由值。 有關(guān)詳細(xì)信息,請(qǐng)參閱使用路由中間件部分或示例應(yīng)用中的示例代碼。

VirtualPathContext 構(gòu)造函數(shù)的第二個(gè)參數(shù)是環(huán)境值的集合。 由于環(huán)境值限制了開發(fā)人員在請(qǐng)求上下文中必須指定的值數(shù),因此環(huán)境值使用起來(lái)很方便。 當(dāng)前請(qǐng)求的當(dāng)前路由值被視為鏈接生成的環(huán)境值。 在 ASP.NET Core MVC 應(yīng)用 HomeController 的 About 操作中,無(wú)需指定控制器路由值即可鏈接到使用 Home 環(huán)境值的 Index 操作—。

忽略與參數(shù)不匹配的環(huán)境值。 在顯式提供的值會(huì)覆蓋環(huán)境值的情況下,也會(huì)忽略環(huán)境值。 在 URL 中將從左到右進(jìn)行匹配。

顯式提供但與路由片段不匹配的值將添加到查詢字符串中。 下表顯示使用路由模板 {controller}/{action}/{id?} 時(shí)的結(jié)果。

環(huán)境值顯式值結(jié)果
控制器 =“Home”操作 =“About”/Home/About
控制器 =“Home”控制器 =“Order”,操作 =“About”/Order/About
控制器 = “Home”,顏色 = “Red”操作 =“About”/Home/About
控制器 =“Home”操作 =“About”,顏色 =“Red”/Home/About?color=Red

如果路由具有不對(duì)應(yīng)于參數(shù)的默認(rèn)值,且該值以顯式方式提供,則它必須與默認(rèn)值相匹配:

C#

routes.MapRoute("blog_route", "blog/{*slug}",
    defaults: new { controller = "Blog", action = "ReadPost" });

當(dāng)提供 controller 和 action 的匹配值時(shí),鏈接生成僅為此路由生成鏈接。

復(fù)雜段

復(fù)雜段(例如,[Route("/x{token}y")])通過(guò)非貪婪的方式從右到左匹配文字進(jìn)行處理。 請(qǐng)參閱此代碼以了解有關(guān)如何匹配復(fù)雜段的詳細(xì)說(shuō)明。 ASP.NET Core 無(wú)法使用代碼示例,但它提供了對(duì)復(fù)雜段的合理說(shuō)明。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)