萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 網路安全 >  >> 網路應用技術

ASP.NET Core MVC 路由系統入門:從傳統路由到屬性路由、約束、命名空間消歧義的完整實作

路由是 ASP.NET Core MVC 的靈魂,將 HTTP 請求映射到控制器動作。本文從零建立專案,逐步講解傳統路由、預設值、靜態片段、自訂參數、約束條件、命名空間消歧義、HTTP 動詞限制等核心概念,並提供可直接複製執行的程式碼範例。

專案建立與基礎設定

dotnet new mvc -n RoutingDemo -o RoutingDemo
cd RoutingDemo
# 刪除 Program.cs / Startup.cs 中預設路由,手工建構以利學習

1. 基礎傳統路由

// Program.cs (或 Startup.cs ConfigureServices)
builder.Services.AddControllersWithViews();

var app = builder.Build();

// 傳統路由註冊 (順序決定優先權,越詳細越優先)
app.MapControllerRoute(
    name: "basic",
    pattern: "Home/Index",           // 靜態 URL 片段
    defaults: new { controller = "Home", action = "Index" });

app.Run();

瀏覽 /Home/Index → 呼叫 HomeController.Index()。

2. 預設路由 — 讓網站根網址直接顯示首頁

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}",  // {id?} 表示選填
    defaults: new { controller = "Home", action = "Index" });
  • {controller=Home}:參數名稱必須與 URL 佔位符同名 (大小寫不敏感)。
  • id?:問號表示選填,若無提供則為 null。

現在 /、/Home、/Home/Index、/Home/Index/123 皆可正確對應。

3. 靜態 URL 片段 — 建立友善、語意化網址

app.MapControllerRoute(
    name: "pretty",
    pattern: "X{controller}-{action}",   // 靜態字元 'X' 與 '-' 直接出現在 URL
    defaults: new { controller = "Home", action = "Index" });

對應 URL:/XHome-Index。移除 X 則無法比對 (除非另有路由允許)。

4. 自訂路由參數 — 捕獲任意片段傳給 Action

app.MapControllerRoute(
    name: "custom",
    pattern: "{controller}/{action}/{id}/{*catchall}",
    defaults: new { controller = "Home", action = "Index" });
  • {id}:必要參數,對應 Action 參數 int id 或 string id。
  • {*catchall}:捕獲剩餘所有片段 (含斜線),對應 string catchall。

Controller 端接收

public class HomeController : Controller
{
    public IActionResult Index(int? id, string catchall)
    {
        ViewBag.Id = id;
        ViewBag.Catchall = catchall;
        return View();
    }
}

View 顯示

@* Views/Home/Index.cshtml *@

Id: @ViewBag.Id

Catchall: @ViewBag.Catchall

瀏覽 /Home/Index/42/a/b/c → Id=42, Catchall="a/b/c"。

5. 解決控制器命名空間衝突 (二義性)

若專案與引用的類別庫皆有 HomeController,路由無法決定使用哪個。

app.MapControllerRoute(
    name: "namespaced",
    pattern: "{controller}/{action}/{id?}",
    defaults: new { controller = "Home", action = "Index" },
    constraints: new { },  // 空約束
    dataTokens: new { namespaces = new[] { "RoutingDemo.Controllers" } });

dataTokens.namespace 限定路由僅掃描指定命名空間,徹底解決衝突。

6. 路由約束 — 限制參數格式

內建約束

約束說明範例
int32 位元整數{id:int}
booltrue/false{flag:bool}
datetimeDateTime 格式{date:datetime}
decimalDecimal{price:decimal}
guidGUID{token:guid}
length(min,max)字串長度範圍{name:length(3,20)}
min/max/range數值範圍{age:range(18,65)}
alpha純字母{code:alpha}
regex(pattern)正則表達式{slug:regex(^[a-z0-9-]+$)}
httpmethod限定 HTTP 動詞httpmethod=GET,POST

套用約束範例

app.MapControllerRoute(
    name: "constrained",
    pattern: "api/products/{id:int:min(1)}",
    defaults: new { controller = "Products", action = "Detail" });

現在 /api/products/abc 將 404,/api/products/10 正常。

7. 自訂路由約束 (IRouteConstraint)

需求:僅允許 Chrome 瀏覽器存取特定路由。

// Constraints/ChromeOnlyConstraint.cs
public class ChromeOnlyConstraint : IRouteConstraint
{
    public bool Match(HttpContext httpContext, IRouter route, string routeKey,
                      RouteValueDictionary values, RouteDirection routeDirection)
    {
        var ua = httpContext.Request.Headers["User-Agent"].ToString();
        return ua.Contains("Chrome") && !ua.Contains("Edg"); // 排除 Edge
    }
}

// 註冊路由
app.MapControllerRoute(
    name: "chromeOnly",
    pattern: "chrome-only/{action}",
    defaults: new { controller = "Home" },
    constraints: new { browser = new ChromeOnlyConstraint() });

注意:User-Agent 可偽造,僅供教學示範,生產環境請用功能偵測或伺服端邏輯。

8. 屬性路由 — 現代推薦做法

在 Controller/Action 直接標註路由,集中管理、直觀、支援 OpenAPI/Swagger 自動生成。

[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
    [HttpGet("{id:int}")]           // GET /api/products/123
    public IActionResult Get(int id) => Ok(new { Id = id });

    [HttpPost]                      // POST /api/products
    public IActionResult Create(ProductDto dto) => Ok(dto);

    [HttpPut("{id:int}")]           // PUT /api/products/123
    public IActionResult Update(int id, ProductDto dto) => Ok();

    [HttpDelete("{id:int}")]        // DELETE /api/products/123
    public IActionResult Delete(int id) => NoContent();
}

啟用:builder.Services.AddControllers(); app.MapControllers(); (取代傳統路由)。

路由匹配順序與優先權規則

  1. 屬性路由優先於傳統路由 (若同時啟用)。
  2. 傳統路由按 MapControllerRoute 呼叫順序:先註冊優先。
  3. 同一路由模板內:靜態片段 > 含約束參數 > 無約束參數 > {*catchall}。
  4. 區分大小寫:預設不區分 (可用 options.LowercaseUrls = true 強制小寫)。

除錯技巧

  • 開發環境啟用 app.UseDeveloperExceptionPage(),錯誤頁會顯示匹配路由詳情。
  • 中介軟體記錄:app.Use(async (ctx, next) => { var endpoint = ctx.GetEndpoint(); logger.LogInfo($"{ctx.Request.Path} -> {endpoint?.DisplayName}"); await next(); });
  • dotnet-route 全域工具:dotnet tool install -g dotnet-route && dotnet-route 列出所有已註冊路由。

總結

ASP.NET Core 路由系統靈活強大:傳統路由適合伺服端渲染 MVC、集中管理;屬性路由適合 Web API、RESTful 設計、Swagger 整合。掌握「預設值、選填參數、靜態片段、Catch-all、命名空間消歧義、內建/自訂約束、HTTP 動詞限制」七大核心技巧,即可駕馭 99% 路由需求。

copyright © 萬盛學電腦網 all rights reserved