ASP.NET Core基礎知識(四)【路由】

風靈使發表於2019-02-18

路由負責將請求 URI 對映到終結點選擇器並向終結點排程傳入的請求。 路由在應用中定義,並在應用啟動時進行配置。 路由可以選擇從請求包含的 URL 中提取值,然後這些值便可用於處理請求。 通過使用應用中的路由資訊,路由還能生成對映到終結點選擇器的 URL

要在 ASP.NET Core 2.2 中使用最新路由方案,請在 Startup.ConfigureServices 中為 MVC 服務註冊指定相容性版本

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

EnableEndpointRouting 選項確定路由是應在內部使用 ASP.NET Core 2.1 或更早版本的基於終結點的邏輯還是使用其基於 IRouter的邏輯。 相容性版本設定為 2.2 或更高版本時,預設值為 true。 將值設定為 false 以使用先前的路由邏輯:

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

有關基於 Microsoft.AspNetCore.Routing.IRouter的路由的詳細資訊,請參閱本主題的ASP.NET Core 2.1 版本

本文件介紹較低階別的 ASP.NET Core 路由。 有關 ASP.NET Core MVC 路由的資訊,請參閱 在 ASP.NET Core 中路由到控制器操作。 有關 Razor Pages 中路由約定的資訊,請參閱 在 ASP.NET Core 中路由到控制器操作

路由基礎知識

大多數應用應選擇基本的描述性路由方案,讓 URL 有可讀性和意義。 預設傳統路由 {controller=Home}/{action=Index}/{id?}

  • 支援基本的描述性路由方案。
  • 是基於 UI 的應用的有用起點。

開發人員通常在專業情況下(例如,部落格和電子商務終結點)使用屬性路由或專用傳統路由嚮應用的高流量區域新增其他簡潔路由。

Web API 應使用屬性路由,將應用功能建模為一組資源,其中操作是由 HTTP 謂詞表示。 也就是說,對同一邏輯資源執行的許多操作(例如,GET、POST)都將使用相同 URL。 屬性路由提供了精心設計 API 的公共終結點佈局所需的控制級別。

Razor Pages 應用使用預設的傳統路由從應用的“頁面”資料夾中提供命名資源。 還可以使用其他約定來自定義 Razor Pages 路由行為。 有關詳細資訊,請參閱 ASP.NET Core 中的 Razor 頁面介紹ASP.NET Core 中 Razor 頁面的路由和應用約定

藉助 URL 生成支援,無需通過硬編碼 URL 將應用關聯到一起,即可開發應用。 此支援允許從基本路由配置入手,並在確定應用的資源佈局後修改路由。

路由使用“終結點”(Endpoint) 來表示應用中的邏輯終結點。

終結點定義用於處理請求的委託和任意後設資料的集合。 後設資料用於實現橫切關注點,該實現基於附加到每個終結點的策略和配置。

路由系統具有以下特徵:

  • 路由模板語法用於通過標記化路由引數來定義路由。

  • 可以使用常規樣式和屬性樣式終結點配置。

  • IRouteConstraint 用於確定 URL 引數是否包含給定的終結點約束的有效值。

  • 應用模型(如 MVC/Razor Pages)註冊其所有終結點,這些終結點具有可預測的路由方案實現。

  • 路由實現會在中介軟體管道中任何所需位置制定路由決策。

  • 路由中介軟體之後出現的中介軟體可以檢查路由中介軟體針對給定請求 URI 的終結點決策結果。

  • 可以在中介軟體管道中的任何位置列舉應用中的所有終結點。

  • 應用可根據終結點資訊使用路由生成 URL(例如,用於重定向或連結),從而避免硬編碼 URL,這有助於可維護性。

  • URL 生成是基於支援任意可擴充套件性的地址:

    • 可以使用依賴關係注入 (DI)在任意位置解析連結生成器 API (LinkGenerator) 以生成 URL。
    • 如果無法通過 DI 獲得連結生成器 API,則 IUrlHelper 會提供生成 URL 的方法。

ASP.NET Core 2.2 中釋出終結點路由後,終結點連結的適用範圍限制為 MVC/Razor Pages 操作和頁面。 將計劃在未來發布的版本中擴充套件終結點連結的功能。

路由通過RouterMiddleware類連線到中介軟體管道。 ASP.NET Core MVC向中介軟體管道新增路由,作為其配置的一部分,並處理 MVC 和 Razor Pages 應用中的路由。 要了解如何將路由用作獨立元件,請參閱使用路由中介軟體部分。

URL 匹配

URL 匹配是路由向終結點排程傳入請求的過程。 此過程基於 URL 路徑中的資料,但可以進行擴充套件以考慮請求中的任何資料。 向單獨的處理程式排程請求的功能是縮放應用的大小和複雜性的關鍵。

終結點路由中的路由系統負責所有的排程決策。 由於中介軟體是基於所選終結點來應用策略,因此任何可能影響排程或安全策略應用的決策都應在路由系統內部制定,這一點很重要。

執行終結點委託時,將根據迄今執行的請求處理將 RouteContext.RouteData 的屬性設定為適當的值。

RouteData.Values是從路由中生成的路由值的字典。 這些值通常通過標記 URL 來確定,可用來接受使用者輸入,或者在應用內作出進一步的排程決策。

RouteData.DataTokens是一個與匹配的路由相關的其他資料的屬性包。 提供 DataTokens 以支援將狀態資料與每個路由相關聯,以便應用可根據所匹配的路由作出決策。 這些值是開發者定義的,不會影響通過任何方式路由的行為。 此外,儲存於 RouteData.DataTokens 中的值可以屬於任何型別,與 RouteData.Values 相反,後者必須能夠轉換為字串,或從字串進行轉換。

RouteData.Routers是參與成功匹配請求的路由的列表。 路由可以相互巢狀。 Routers 屬性可以通過導致匹配的邏輯路由樹反映該路徑。 通常情況下,Routers 中的第一項是路由集合,應該用於生成 URLRouters 中的最後一項是匹配的路由處理程式。

URL 生成

URL 生成是通過其可根據一組路由值建立 URL 路徑的過程。 這需要考慮終結點與訪問它們的 URL 之間的邏輯分隔。

終結點路由包含連結生成器 API (LinkGenerator)。 LinkGenerator 是可從 DI 檢索的單一例項服務。 該 API 可在執行請求的上下文之外使用。 MVC 的 IUrlHelper 和依賴 IUrlHelper 的方案(如 Tag HelpersHTML HelpersAction Results)使用連結生成器提供連結生成功能。

連結生成器基於“地址”和“地址方案”概念。 地址方案是確定哪些終結點用於連結生成的方式。 例如,許多使用者熟悉的來自 MVC/Razor Pages 的路由名稱和路由值方案都是作為地址方案實現的。

連結生成器可以通過以下擴充套件方法連結到 MVC/Razor Pages 操作和頁面:

  • GetPathByAction
  • GetUriByAction
  • GetPathByPage
  • GetUriByPage

這些方法的過載接受包含 HttpContext 的引數。 這些方法在功能上等同於 Url.ActionUrl.Page,但提供了更大的靈活性和更多的選項。

GetPath* 方法與 Url.ActionUrl.Page 最相似,因為它們生成包含絕對路徑的 URI。 GetUri* 方法始終生成包含方案和主機的絕對 URI。 接受 HttpContext 的方法在執行請求的上下文中生成 URI。 除非重寫,否則將使用來自執行請求的環境路由值、URL 基本路徑、方案和主機。

使用地址呼叫 LinkGenerator。 生成 URI 的過程分兩步進行:

  1. 將地址繫結到與地址匹配的終結點列表。
  2. 計算每個終結點的 RoutePattern,直到找到與提供的值匹配的路由模式。 輸出結果會與提供給連結生成器的其他 URI 部分進行組合並返回。

對任何型別的地址,LinkGenerator 提供的方法均支援標準連結生成功能。 使用連結生成器的最簡便方法是通過擴充套件方法對特定地址型別執行操作。

擴充套件方法 說明
GetPathByAddress 根據提供的值生成具有絕對路徑的 URI。
GetUriByAddress 根據提供的值生成絕對 URI。

警告
請注意有關呼叫 LinkGenerator 方法的下列含義:

  • 對於不驗證傳入請求的 Host 標頭的應用配置,請謹慎使用 GetUri* 擴充套件方法。 如果未驗證傳入請求的 Host標頭,則可能以檢視/頁面中 URI 的形式將不受信任的請求輸入傳送回客戶端。 建議所有生產應用都將其伺服器配置為針對已知有效值驗證 Host 標頭。

  • 在中介軟體中將 LinkGeneratorMapMapWhen 結合使用時,請小心謹慎。 Map* 會更改執行請求的基路徑,這會影響連結生成的輸出。 所有 LinkGenerator API 都允許指定基路徑。 始終指定一個空的基路徑來撤消 Map* 對連結生成的影響。

與早期版本路由的差異

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

  • 終結點路由系統不支援基於 IRouter 的可擴充套件性,包括從 Route 繼承。

  • 終結點路由不支援 WebApiCompatShim。 使用 2.1 相容性版本(.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)) 以繼續使用相容性填充碼。

  • 在使用傳統路由時,終結點路由對生成的 URI 的大小寫具有不同的行為。

    請考慮以下預設路由模板:

    app.UseMvc(routes =>
    {
        routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
    });
    

    假設使用以下路由生成某個操作的連結:

    var link = Url.Action("ReadPost", "blog", new { id = 17, });
    

    如果使用基於 IRouter 的路由,此程式碼生成 URI /blog/ReadPost/17,該 URI 遵循所提供路由值的大小寫。 ASP.NET Core 2.2 或更高版本中的終結點路由生成 /Blog/ReadPost/17(“Blog”大寫)。 終結點路由提供 IOutboundParameterTransformer 介面,可用於在全域性範圍自定義此行為或應用不同的約定來對映 URL。

    有關詳細資訊,請參閱引數轉換器參考

  • 在試圖連結到不存在的控制器/操作或頁面時,MVC/Razor Pages 通過傳統路由執行的連結生成,其操作具有不同的行為。

    請考慮以下預設路由模板:

    app.UseMvc(routes =>
    {
        routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
    });
    

    假設使用以下路由通過預設模板生成某個操作的連結:

    var link = Url.Action("ReadPost", "Blog", new { id = 17, });
    

    如果使用基於 IRouter 的路由,即使 BlogController 不存在或沒有 ReadPost 操作方法,結果也始終為 /Blog/ReadPost/17。 正如所料,如果操作方法存在,ASP.NET Core 2.2 或更高版本中的終結點路由會生成 /Blog/ReadPost/17。 但是,如果操作不存在,終結點路由會生成空字串。 從概念上講,如果操作不存在,終結點路由不會假定終結點存在。

  • 與終結點路由一起使用時,連結生成環境值失效演算法的行為會有所不同。

    環境值失效是一種演算法,用於決定當前正在執行的請求(環境值)中的哪些路由值可用於連結生成操作。 連結到不同操作時,傳統路由會使額外的路由值失效。 ASP.NET Core 2.2 之前的版本中,屬性路由不具有此行為。 在 ASP.NET Core 的早期版本中,如果有另一個操作使用同一路由引數名稱,則該操作的連結會導致發生連結生成錯誤。 在 ASP.NET Core 2.2 或更高版本中,連結到另一個操作時,這兩種路由形式都會使值失效。

    請考慮 ASP.NET Core2.1 或更高版本中的以下示例。 連結到另一個操作(或另一頁面)時,路由值可能會按非預期的方式被重用。

    /Pages/Store/Product.cshtml 中:

    @page "{id}"
    @Url.Page("/Login")
    

    在 /Pages/Login.cshtml 中:

    @page "{id?}"
    

    如果 ASP.NET Core 2.1 或更早版本中的 URI 為 /Store/Product/18,則由 @Url.Page("/Login") 在 Store/Info 頁面上生成的連結為 /Login/18。 即使連結目標完全是應用的其他部分,仍會重用 id 值 18。 /Login 頁面上下文中的 id 路由值可能是使用者 ID 值,而非儲存產品 ID 值。

    ASP.NET Core 2.2 或更高版本的終結點路由中,結果為 /Login。 當連結的目標是另一個操作或頁面時,不會重複使用環境值。

  • 往返路由引數語法:使用雙星號 (**) catch-all 引數語法時,不對正斜槓進行編碼。

    在連結生成期間,路由系統對雙星號 (**) catch-all 引數(例如,{**myparametername})中捕獲的除正斜槓外的值進行編碼。 在 ASP.NET Core 2.2 或更高版本中,基於 IRouter 的路由支援雙星號 catch-all 引數。

    ASP.NET Core ({*myparametername}) 早期版本中的單星號 catch-all 引數語法仍然受支援,並對正斜槓進行編碼。

    路由 連結生成方式為
    Url.Action(new { category = "admin/products" })
    /search/{*page} /search/admin%2Fproducts(對正斜槓進行編碼)
    /search/{**page} /search/admin/products

中介軟體示例

在以下示例中,中介軟體使用 LinkGenerator API 建立列出儲存產品的操作方法的連結。 應用中的任何類都可通過將連結生成器注入類並呼叫 GenerateLink 來使用連結生成器。

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.");
    }
}

建立路由

路由提供Route類,作為 IRouter的標準實現。 Route 使用 route template 語法來定義模式,以便在呼叫RouteAsync時匹配 URL 路徑。 呼叫 GetVirtualPath 時,Route 使用同一路由模板生成 URL。

大多數應用通過呼叫MapRouteIRouteBuilder上定義的一種類似的擴充套件方法來建立路由。 任何 IRouteBuilder 擴充套件方法都會建立Route的例項並將其新增到路由集合中。

MapRoute 不接受路由處理程式引數。 MapRoute 僅新增由 DefaultHandler處理的路由。 要了解 MVC 中的路由的詳細資訊,請參閱在 ASP.NET Core 中路由到控制器操作

以下程式碼示例是典型 ASP.NET Core MVC 路由定義使用的一個 MapRoute 呼叫示例:

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

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

路由值是通過將 URL 路徑拆分成段,並且將每段與路由模板中的路由引數名稱相匹配來確定的。 路由引數已命名。 引數通過將引數名稱括在大括號 { ... } 中來定義。

上述模板還可匹配 URL 路徑 /,並且生成值 { controller = Home, action = Index }。 這是因為 {controller}{action} 路由引數具有預設值,且 id 路由引數是可選的。 路由引數名稱為引數定義預設值後,等號 (=) 後將有一個值。 路由引數名稱後面的問號 (?) 定義了可選引數。

路由匹配時,具有預設值的路由引數始終會生成路由值。 如果沒有相應的 URL 路徑段,則可選引數不會生成路由值。 有關路由模板方案和語法的詳細說明,請參閱路由模板參考部分。

在以下示例中,路由引數定義 {id:int}id 路由引數定義路由約束

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

此模板與類似於 /Products/Details/17 而不是 /Products/Details/Apples 的 URL 路徑相匹配。 路由約束實現 IRouteConstraint並檢查路由值,以驗證它們。 在此示例中,路由值 id 必須可轉換為整數。 有關框架提供的路由約束的說明,請參閱路由約束參考

其他 MapRoute 過載接受 constraintsdataTokensdefaults 的值。 這些引數的典型用法是傳遞匿名型別化物件,其中匿名型別的屬性名匹配路由引數名稱。

以下 MapRoute 示例可建立等效路由:

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?}");

定義約束和預設值的內聯語法對於簡單路由很方便。 但是,存在內聯語法不支援的方案,例如資料令牌。

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

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 }controlleraction 的預設路由值由路由生成,即便模板中沒有對應的路由引數。 可在路由模板中指定預設值。 根據路由引數名稱前的雙星號 (**) 外觀,article 路由引數被定義為 catch-all。 全方位路由引數可捕獲 URL 路徑的其餘部分,也能匹配空白字串。

以下示例新增了路由約束和資料令牌:

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 } 和資料令牌 { locale = en-US }

在這裡插入圖片描述

路由類 URL 生成

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

提示
為更好了解 URL 生成,請想象你要生成的 URL,然後考慮路由模板將如何匹配該 URL。 將生成什麼值? 這大致相當於 URL 在 Route 類中的生成方式。

以下示例使用常規 ASP.NET Core MVC 預設路由:

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

使用路由值 { controller = Products, action = List },將生成 URL /Products/List。 路由值將替換為相應的路由引數,以形成 URL 路徑。 由於 id 是可選路由引數,因此成功生成的 URL 不具有 id 的值。

使用路由值 { controller = Home, action = Index },將生成 URL /。 提供的路由值與預設值匹配,並且會安全地省略與預設值對應的段。

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

使用 ASP.NET Core MVC 應用應該使用 xref:Microsoft.AspNetCore.Mvc.Routing.UrlHelper 生成 URL,而不是直接呼叫到路由。

有關 URL 生成的詳細資訊,請參閱 Url 生成參考部分。

使用路由中介軟體

引用應用專案檔案中的 Microsoft.AspNetCore.App 元包

將路由新增到 Startup.ConfigureServices 中的服務容器:

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

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

  • RouteBuilder
  • MapGet -僅匹配 HTTP GET 請求。
  • UseRouter
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 的響應。

URI 響應
/package/create/3 Hello! Route values: [operation, create], [id, 3]
/package/track/-3 Hello! Route values: [operation, track], [id, -3]
/package/track/-3/ Hello! Route values: [operation, track], [id, -3]
/package/track/ 請求失敗,不匹配。
GET /hello/Joe Hi, Joe!
POST /hello/Joe 請求失敗,僅匹配 HTTP GET。
GET /hello/Joe/Smith 請求失敗,不匹配。

框架可提供一組用於建立路由( RequestDelegateRouteBuilderExtensions) 的擴充套件方法:

  • MapDelete
  • MapGet
  • MapMiddlewareDelete
  • MapMiddlewareGet
  • MapMiddlewarePost
  • MapMiddlewarePut
  • MapMiddlewareRoute
  • MapMiddlewareVerb
  • MapPost
  • MapPut
  • MapRoute
  • MapVerb

Map[Verb] 方法將使用約束來將路由限制為方法名稱中的 HTTP 謂詞。 有關示例,請參閱MapGetMapVerb

路由模板參考

如果路由找到匹配項,大括號 ({ ... }) 內的令牌定義繫結的路由引數。 可在路由段中定義多個路由引數,但必須由文字值隔開。 例如,{controller=Home}{action=Index} 不是有效的路由,因為 {controller}{action} 之間沒有文字值。 這些路由引數必須具有名稱,且可能指定了其他屬性。

路由引數以外的文字(例如 {id})和路徑分隔符 / 必須匹配 URL 中的文字。 文字匹配區分大小寫,並且基於 URL 路徑已解碼的表示形式。 要匹配文字路由引數分隔符({}),請通過重複該字元({{}})來轉義分隔符。

嘗試捕獲具有可選副檔名的檔名的 URL 模式還有其他注意事項。 例如,考慮模板 files/{filename}.{ext?}。 當 filenameext 的值都存在時,將填充這兩個值。 如果 URL 中僅存在 filename 的值,則路由匹配,因為尾隨句點 (.) 是可選的。 以下 URL 與此路由相匹配:

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

可以使用星號 (*) 或雙星號 (**) 作為路由引數的字首,以繫結到 URI 的其餘部分。 這些稱為 catch-all 引數。 例如,blog/{**slug} 將匹配以 /blog 開頭且其後帶有任何值(將分配給 slug 路由值)的 URI。 全方位引數還可以匹配空字串。

使用路由生成 URL(包括路徑分隔符 (/))時,catch-all 引數會轉義相應的字元。 例如,路由值為 { path = "my/path" } 的路由 foo/{*path} 生成 foo/my%2Fpath。 請注意轉義的正斜槓。 要往返路徑分隔符,請使用 ** 路由引數字首。 { path = "my/path" } 的路由 foo/{**path} 生成 foo/my/path

路由引數可能具有指定的預設值,方法是在引數名稱後使用等號 (=) 隔開以指定預設值。 例如,{controller=Home}Home 定義為 controller 的預設值。 如果引數的 URL 中不存在任何值,則使用預設值。 通過在引數名稱的末尾附加問號 (?) 可使路由引數成為可選項,如 id? 中所示。 可選值和預設路徑引數的區別在於具有預設值的路由引數始終會生成值 —,而可選引數僅當請求 URL 提供值時才會具有值。

路由引數可能具有必須與從 URL 中繫結的路由值匹配的約束。 在路由引數後面新增一個冒號 (:) 和約束名稱可指定路由引數上的內聯約束。 如果約束需要引數,將以在約束名稱後括在括號 ((...)) 中的形式提供。 通過追加另一個冒號 (:) 和約束名稱,可指定多個內聯約束。

約束名稱和引數將傳遞給 IInlineConstraintResolver服務,以建立 IRouteConstraint的例項,用於處理 URL。 例如,路由模板 blog/{article:minlength(10)} 使用引數 10 指定 minlength 約束。 有關路由約束詳情以及框架提供的約束列表,請參閱路由約束引用部分。

路由引數還可以具有引數轉換器,用於在生成連結以及將操作和頁面匹配到 URI 時轉換引數的值。 與約束類似,可以通過在路由引數名稱後面新增冒號 (:) 和轉換器名稱,將引數變換器內聯新增到路徑引數。 例如,路由模板 blog/{article:slugify} 指定 slugify 轉換器。 有關引數轉換的詳細資訊,請參閱引數轉換器參考部分。

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

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

使用模板通常是進行路由最簡單的方法。 還可在路由模板外指定約束和預設值。

提示
啟用日誌記錄以檢視內建路由實現(如 Route)如何匹配請求。

保留的路由名稱

以下關鍵字是保留的名稱,它們不能用作路由名稱或引數:

  • action
  • area
  • controller
  • handler
  • page

路由約束參考

路由約束在傳入 URL 發生匹配時執行,URL 路徑標記為路由值。 路徑約束通常檢查通過路徑模板關聯的路徑值,並對該值是否可接受做出是/否決定。 某些路由約束使用路由值以外的資料來考慮是否可以路由請求。 例如,HttpMethodRouteConstraint可以根據其 HTTP 謂詞接受或拒絕請求。 約束用於路由請求和連結生成。

警告
請勿將約束用於“輸入驗證”。 如果將約束用於“輸入約束”,那麼無效輸入將導致“404 - 未找到”響應,而不是含相應錯誤訊息的“400 - 錯誤請求”。 路由約束用於消除類似路由的歧義,而不是驗證特定路由的輸入。

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

約束 示例 匹配項示例 說明
int {id:int} 123456789-123456789 匹配任何整數
bool {active:bool} trueFALSE 匹配 truefalse(區分大小寫)
datetime {dob:datetime} 2016-12-312016-12-31 7:32pm 匹配有效的 DateTime 值(位於固定區域性中 - 檢視警告)
decimal {price:decimal} 49.99-1,000.01 匹配有效的 decimal 值(位於固定區域性中 - 檢視警告)
double {weight:double} 1.234-1,001.01e8 匹配有效的 double 值(位於固定區域性中 - 檢視警告)
float {weight:float} 1.234-1,001.01e8 匹配有效的 float 值(位於固定區域性中 - 檢視警告)
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 個字元
maxlength(value) {filename:maxlength(8)} Richard 字串不得超過 8 個字元
length(length) {filename:length(12)} somefile.txt 字串必須正好為 12 個字元
length(min,max) {filename:length(8,16)} somefile.txt 字串必須至少為 8 個字元,且不得超過 16 個字元
min(value) {age:min(18)} 19 整數值必須至少為 18
max(value) {age:max(120)} 91 整數值不得超過 120
range(min,max) {age:range(18,120)} 91 整數值必須至少為 18,且不得超過 120
alpha {name:alpha} Rick 字串必須由一個或多個字母字元(a-z,區分大小寫)組成
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 字串必須匹配正規表示式(參見有關定義正規表示式的提示)
required {name:required} Rick 用於強制在 URL 生成過程中存在非引數值

可向單個引數應用多個由冒號分隔的約束。 例如,以下約束將引數限制為大於或等於 1 的整數值:

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

警告
驗證 URL 的路由約束並將轉換為始終使用固定區域性的 CLR 型別(例如 intDateTime)。 這些約束假定 URL 不可本地化。 框架提供的路由約束不會修改儲存於路由值中的值。 從 URL 中分析的所有路由值都將儲存為字串。 例如,float 約束會嘗試將路由值轉換為浮點數,但轉換後的值僅用來驗證其是否可轉換為浮點數。

正規表示式

ASP.NET Core 框架將向正規表示式建構函式新增 RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant。 有關這些成員的說明,請參閱RegexOptions

正規表示式與路由和 C# 計算機語言使用的分隔符和令牌相似。 必須對正規表示式令牌進行轉義。 要在路由中使用正規表示式 ^\d{3}-\d{2}-\d{4}$,表示式必須在字串中提供 \(單反斜槓)字元,正如 C# 原始檔中的 \\(雙反斜槓)字元一樣,以便對 \ 字串轉義字元進行轉義(除非使用字串文字)。 要對路由引數分隔符進行轉義({}[]),請將表示式({{}[[]])中的字元數加倍。 下表展示了正規表示式和轉義版本。

正規表示式 轉義後的正規表示式
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

路由中使用的正規表示式通常以脫字號 (^) 開頭,並匹配字串的起始位置。 表示式通常以美元符號 ($) 字元結尾,並匹配字串的結尾。 ^$ 字元可確保正規表示式匹配整個路由引數值。 如果沒有 ^$ 字元,正規表示式將匹配字串內的所有子字串,而這通常是不需要的。 下表提供了示例並說明了它們匹配或匹配失敗的原因。

表示式 String 匹配 註釋
[a-z]{2} hello 子字串匹配
[a-z]{2} 123abc456 子字串匹配
[a-z]{2} mz 匹配表示式
[a-z]{2} MZ 不區分大小寫
^[a-z]{2}$ hello No 參閱上述 ^$
^[a-z]{2}$ 123abc456 No 參閱上述 ^$

有關正規表示式語法的詳細資訊,請參閱 .NET Framework 正規表示式

若要將引數限制為一組已知的可能值,可使用正規表示式。 例如,{action:regex(^(list|get|create)$)} 僅將 action 路由值匹配到 listgetcreate。 如果傳遞到約束字典中,字串 ^(list|get|create)$ 將等效。 已傳遞到約束字典(不與模板內聯)且不匹配任何已知約束的約束還將被視為正規表示式。

自定義路由約束

除了內建路由約束以外,還可以通過實現 IRouteConstraint 介面來建立自定義路由約束。 IRouteConstraint 介面包含一個方法 Match,當滿足約束時,它返回 true,否則返回 false

若要使用自定義 IRouteConstraint,必須在應用的服務容器中使用應用的 RouteOptions.ConstraintMap 註冊路由約束型別。 ConstraintMap是將路由約束鍵對映到驗證這些約束的 IRouteConstraint 實現的目錄。 應用的 RouteOptions.ConstraintMap 可作為 services.AddRouting 呼叫的一部分在 Startup.ConfigureServices 中進行更新,也可以通過使用 services.Configure<RouteOptions> 直接配置 RouteOptions 進行更新。 例如:

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

然後,可以使用在註冊約束型別時指定的名稱,以常規方式將約束應用於路由。 例如:

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

引數轉換器參考

引數轉換器:

  • 在為 Route 生成連結時執行。
  • 實現 Microsoft.AspNetCore.Routing.IOutboundParameterTransformer
  • 使用 ConstraintMap 進行配置。
  • 獲取引數的路由值並將其轉換為新的字串值。
  • 在生成的連結中使用轉換後的值的結果。

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

若要在路由模式中使用引數轉換器,請先在 Startup.ConfigureServices 中使用 ConstraintMap對其進行配置:

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

框架使用引數轉化器來轉換進行終結點解析的 URI。 例如,ASP.NET Core MVC 使用引數轉換器來轉換用於匹配 area``controller``actionpage 的路由值。

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

使用上述路由,操作 SubscriptionManagementController.GetAll() 與 URI /subscription-management/get-all 匹配。 引數轉換器不會更改用於生成連結的路由值。 例如,Url.Action("GetAll", "SubscriptionManagement") 輸出 /subscription-management/get-all

對於結合使用引數轉換器和所生成的路由,ASP.NET Core 提供了 API 約定:

  • ASP.NET Core MVC 還具有 Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention API 約定。 該約定將指定的引數轉換器應用於應用中的所有屬性路由。 在替換屬性路徑令牌時,引數轉換器將轉換這些令牌。 有關詳細資訊,請參閱使用引數轉換器自定義標記替換
  • Razor Pages 具有 Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteTransformerConvention API 約定。 此約定將指定的引數轉換器應用於所有自動發現的 Razor Pages。 引數轉換器轉換 Razor Pages.路由的資料夾和檔名段。 有關詳細資訊,請參閱使用引數轉換器自定義頁面路由

URL 生成參考

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

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}operationid 路由值。 有關詳細資訊,請參閱使用路由中介軟體部分或示例應用中的示例程式碼。

VirtualPathContext 建構函式的第二個引數是環境值的集合。 由於環境值限制了開發人員在請求上下文中必須指定的值數,因此環境值使用起來很方便。 當前請求的當前路由值被視為連結生成的環境值。 在 ASP.NET Core MVC 應用 HomeControllerAbout 操作中,無需指定控制器路由值即可連結到使用 Home 環境值的 Index 操作。

忽略與引數不匹配的環境值。 在顯式提供的值會覆蓋環境值的情況下,也會忽略環境值。 在 URL 中將從左到右進行匹配。

顯式提供但與路由片段不匹配的值將新增到查詢字串中。 下表顯示使用路由模板 {controller}/{action}/{id?} 時的結果。

環境值 顯式值 結果
控制器 =“Home” 操作 =“About” /Home/About
控制器 =“Home” 控制器 =“Order”,操作 =“About” /Order/About
控制器 = “Home”,顏色 = “Red” 操作 =“About” /Home/About
控制器 =“Home” 操作 =“About”,顏色 =“Red” /Home/About?color=Red

如果路由具有不對應於引數的預設值,且該值以顯式方式提供,則它必須與預設值相匹配:

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

當提供 controlleraction 的匹配值時,連結生成僅為此路由生成連結。

複雜段

複雜段(例如,[Route("/x{token}y")])通過非貪婪的方式從右到左匹配文字進行處理。 請參閱此程式碼以瞭解有關如何匹配複雜段的詳細說明。 ASP.NET Core 無法使用程式碼示例,但它提供了對複雜段的合理說明。

相關文章