2.3.3 Web API -- 路由與終結點
- 路由模闆
- 約定路由
- 特性路由
- 路由沖突
- 終結點
ASP.NET Core 中的路由:
https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/routing?view=aspnetcore-5.0UseRouting 添加路由中間件到管道,路由中間件用來比對 url 和具體的 endpoint,然後執行 endpoint
UseEndpoints 添加或者注冊 endpoint 到程式中,使得路由中間件可以發現它們
- MapRazorPages for Razor Pages 添加所有 Razor Pages 終結點
- MapControllers for controllers 添加所有 controller 終結點
- MapHub for SignalR 添加 SignalR 終結點
- MapGrpcService for gRPC 添加 gRPC 終結點
路由模闆由 token 和其他特定字元組成。比如“/”,特定字元進行路由比對的時候必須全部比對
/hello/{name:alpha}
{name:alpha} 是一段 token,一段 token 包括一個參數名,可以跟着一個限制(alpha)或者一個預設值(mingson),比如 {name=mingson} ,或者直接 {name}
app.UseEndpoints(endpoints =>
{
//endpoints.MapControllers();
endpoints.MapGet("/hello/{name:alpha}", async context =>
{
var name = context.Request.RouteValues["name"];
await context.Response.WriteAsync($"Hello {name}!");
});
});
路由模闆中的參數被存儲在 HttpRequest.RouteValues 中
大小寫不敏感
url 中如果有符合,在模闆中用{}代替
catch-all 路由模闆
- 在 token 前用 或者 加在參數名前,比如 blog/{slug}
- blog/ 後面的字元串會當成 slug 的路由參數值,包括 "/",比如浏覽器輸入 blog/my/path 會比對成 foo/my%2Fpath,如果想要得到 blog/my/path 則使用兩個 ,foo/{path}
- 字元串.也是可選的,比如 files/{filename}.{ext?},如果要輸入 /files/myFile 也能比對到這個路由
//app.Run(async context =>
//{
// await context.Response.WriteAsync("my middleware 2");
//});
app.UseEndpoints(endpoints =>
{
//endpoints.MapControllers();
// 将終結點綁定到路由上
endpoints.MapGet("/hello", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
啟動程式,通路:
https://localhost:5001/hello輸出如下:
my middleware 1Hello World!
擷取路由模闆參數
endpoints.MapGet("/blog/{*title}", async context =>
{
var title = context.Request.RouteValues["title"];
await context.Response.WriteAsync($"blog title: {title}");
});
https://localhost:5001/blog/my-title my middleware 1blog title: my-title
constraint 限制
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
context =>
{
return context.Response.WriteAsync("inline-constraint match");
});
});
預設
endpoints.MapDefaultControllerRoute();
自定義
endpoints.MapControllerRoute("default","{controller=Home}/{action=Index}/{id?}");
// 約定路由
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
// 約定路由也可以同時定義多個
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "blog",
pattern: "blog/{*article}",
defaults: new {controller = "blog", action = "Article"});
});
controller
[Route("[controller]")]
http method
[HttpGet("option")]
[HttpGet]
[Route("option")]
[HttpGet]
[Route("option/{id:int}")]
[HttpGet]
//[Route("option")]
public IActionResult GetOption()
{
return Ok(_myOption);
}
如果路由相同,啟動程式會報錯:
AmbiguousMatchException: The request matched multiple endpoints. Matches:
HelloApi.Controllers.ConfigController.GetOption (HelloApi)
HelloApi.Controllers.ConfigController.GetConfigurations (HelloApi)
ASP.NET Core 終結點是:
- 可執行:具有 RequestDelegate。
- 可擴充:具有中繼資料集合。
- Selectable:可選擇性包含路由資訊。
- 可枚舉:可通過從 DI 中檢索 EndpointDataSource 來列出終結點集合。
終結點可以:
- 通過比對 URL 和 HTTP 方法來選擇。
- 通過運作委托來執行。
中間件的每一步都在比對終結點,是以路由和終結點之間的中間件可以拿到終結點的資訊
app.UseRouting();
// 路由和終結點之間的中間件可以拿到終結點的資訊
app.Use(next => context =>
{
// 擷取目前已經被選擇的終結點
var endpoint = context.GetEndpoint();
if (endpoint is null)
{
return Task.CompletedTask;
}
// 輸出終結點的名稱
Console.WriteLine($"Endpoint: {endpoint.DisplayName}");
// 列印終結點比對的路由
if (endpoint is RouteEndpoint routeEndpoint)
{
Console.WriteLine("Endpoint has route pattern: " +
routeEndpoint.RoutePattern.RawText);
}
// 列印終結點的中繼資料
foreach (var metadata in endpoint.Metadata)
{
Console.WriteLine($"Endpoint has metadata: {metadata}");
}
return Task.CompletedTask;
});
app.UseEndpoints(endpoints =>
{
//endpoints.MapControllers();
// 将終結點綁定到路由上
endpoints.MapGet("/blog/{title}", async context =>
{
var title = context.Request.RouteValues["title"];
await context.Response.WriteAsync($"blog title: {title}");
});
});
https://localhost:5001/blog/my-first-blog 控制台輸出如下:
Endpoint: /blog/{title} HTTP: GET
Endpoint has route pattern: /blog/{title}
Endpoint has metadata: System.Runtime.CompilerServices.AsyncStateMachineAttribute
Endpoint has metadata: System.Diagnostics.DebuggerStepThroughAttribute
Endpoint has metadata: Microsoft.AspNetCore.Routing.HttpMethodMetadata
列印 http 方法
// 列印終結點的中繼資料
foreach (var metadata in endpoint.Metadata)
{
Console.WriteLine($"Endpoint has metadata: {metadata}");
// 列印 http 方法
if (metadata is HttpMethodMetadata httpMethodMetadata)
{
Console.WriteLine($"Current Http Method: {httpMethodMetadata.HttpMethods.FirstOrDefault()}");
}
}
Current Http Method: GET
修改終結點名稱、中繼資料
app.UseEndpoints(endpoints =>
{
//endpoints.MapControllers();
// 将終結點綁定到路由上
endpoints.MapGet("/blog/{title}", async context =>
{
var title = context.Request.RouteValues["title"];
await context.Response.WriteAsync($"blog title: {title}");
}).WithDisplayName("Blog")// 修改名稱
.WithMetadata("10001");// 修改中繼資料
});
- 調用 UseRouting 之前,終結點始終為 null。
- 如果找到比對項,則 UseRouting 和 UseEndpoints 之間的終結點為非 null。
- 如果找到比對項,則 UseEndpoints 中間件即為終端。 稍後會在本文檔中定義終端中間件。
- 僅當找不到比對項時才執行 UseEndpoints 後的中間件。