.NET 云原生架構(gòu)師訓(xùn)練營(模塊二 基礎(chǔ)鞏固 路由與終結(jié)點(diǎn))--學(xué)習(xí)筆記
2.3.3 Web API -- 路由與終結(jié)點(diǎn)
路由模板
約定路由
特性路由
路由沖突
終結(jié)點(diǎn)
ASP.NET Core 中的路由:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/routing?view=aspnetcore-5.0
UseRouting 添加路由中間件到管道,路由中間件用來匹配 url 和具體的 endpoint,然后執(zhí)行 endpoint
UseEndpoints 添加或者注冊 endpoint 到程序中,使得路由中間件可以發(fā)現(xiàn)它們
MapRazorPages for Razor Pages 添加所有 Razor Pages 終結(jié)點(diǎn)
MapControllers for controllers 添加所有 controller 終結(jié)點(diǎn)
MapHub
for SignalR 添加 SignalR 終結(jié)點(diǎn) MapGrpcService
for gRPC 添加 gRPC 終結(jié)點(diǎn)
路由模板
路由模板由 token 和其他特定字符組成。比如“/”,特定字符進(jìn)行路由匹配的時(shí)候必須全部匹配
/hello/{name:alpha}
{name:alpha} 是一段 token,一段 token 包括一個(gè)參數(shù)名,可以跟著一個(gè)約束(alpha)或者一個(gè)默認(rèn)值(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}!");
});
});
路由模板中的參數(shù)被存儲在 HttpRequest.RouteValues 中
大小寫不敏感
url 中如果有符合,在模板中用{}代替
catch-all 路由模板
在 token 前用 * 或者 ** 加在參數(shù)名前,比如 blog/{*slug}
blog/ 后面的字符串會(huì)當(dāng)成 slug 的路由參數(shù)值,包括 "/",比如瀏覽器輸入 blog/my/path 會(huì)匹配成 foo/my%2Fpath,如果想要得到 blog/my/path 則使用兩個(gè) ,foo/{path}
字符串.也是可選的,比如 files/{filename}.{ext?},如果要輸入 /files/myFile 也能匹配到這個(gè)路由
//app.Run(async context =>
//{
// await context.Response.WriteAsync("my middleware 2");
//});
app.UseEndpoints(endpoints =>
{
//endpoints.MapControllers();
// 將終結(jié)點(diǎn)綁定到路由上
endpoints.MapGet("/hello", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
啟動(dòng)程序,訪問:https://localhost:5001/hello
輸出如下:
my middleware 1Hello World!
獲取路由模板參數(shù)
endpoints.MapGet("/blog/{*title}", async context =>
{
var title = context.Request.RouteValues["title"];
await context.Response.WriteAsync($"blog title: {title}");
});
啟動(dòng)程序,訪問: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");
});
});
約定路由
默認(rèn)
endpoints.MapDefaultControllerRoute();
自定義
endpoints.MapControllerRoute("default","{controller=Home}/{action=Index}/{id?}");
// 約定路由
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
// 約定路由也可以同時(shí)定義多個(gè)
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);
}
如果路由相同,啟動(dòng)程序會(huì)報(bào)錯(cuò):
AmbiguousMatchException: The request matched multiple endpoints. Matches:
HelloApi.Controllers.ConfigController.GetOption (HelloApi)
HelloApi.Controllers.ConfigController.GetConfigurations (HelloApi)
終結(jié)點(diǎn)
ASP.NET Core 終結(jié)點(diǎn)是:
可執(zhí)行:具有 RequestDelegate。
可擴(kuò)展:具有元數(shù)據(jù)集合。
Selectable:可選擇性包含路由信息。
可枚舉:可通過從 DI 中檢索 EndpointDataSource 來列出終結(jié)點(diǎn)集合。
終結(jié)點(diǎn)可以:
通過匹配 URL 和 HTTP 方法來選擇。
通過運(yùn)行委托來執(zhí)行。

中間件的每一步都在匹配終結(jié)點(diǎn),所以路由和終結(jié)點(diǎn)之間的中間件可以拿到終結(jié)點(diǎn)的信息
app.UseRouting();
// 路由和終結(jié)點(diǎn)之間的中間件可以拿到終結(jié)點(diǎn)的信息
app.Use(next => context =>
{
// 獲取當(dāng)前已經(jīng)被選擇的終結(jié)點(diǎn)
var endpoint = context.GetEndpoint();
if (endpoint is null)
{
return Task.CompletedTask;
}
// 輸出終結(jié)點(diǎn)的名稱
Console.WriteLine($"Endpoint: {endpoint.DisplayName}");
// 打印終結(jié)點(diǎn)匹配的路由
if (endpoint is RouteEndpoint routeEndpoint)
{
Console.WriteLine("Endpoint has route pattern: " +
routeEndpoint.RoutePattern.RawText);
}
// 打印終結(jié)點(diǎn)的元數(shù)據(jù)
foreach (var metadata in endpoint.Metadata)
{
Console.WriteLine($"Endpoint has metadata: {metadata}");
}
return Task.CompletedTask;
});
app.UseEndpoints(endpoints =>
{
//endpoints.MapControllers();
// 將終結(jié)點(diǎn)綁定到路由上
endpoints.MapGet("/blog/{title}", async context =>
{
var title = context.Request.RouteValues["title"];
await context.Response.WriteAsync($"blog title: {title}");
});
});
啟動(dòng)程序,訪問: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 方法
// 打印終結(jié)點(diǎn)的元數(shù)據(jù)
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()}");
}
}
啟動(dòng)程序,訪問:https://localhost:5001/blog/my-first-blog
控制臺輸出如下:
Current Http Method: GET
修改終結(jié)點(diǎn)名稱、元數(shù)據(jù)
app.UseEndpoints(endpoints =>
{
//endpoints.MapControllers();
// 將終結(jié)點(diǎn)綁定到路由上
endpoints.MapGet("/blog/{title}", async context =>
{
var title = context.Request.RouteValues["title"];
await context.Response.WriteAsync($"blog title: {title}");
}).WithDisplayName("Blog")// 修改名稱
.WithMetadata("10001");// 修改元數(shù)據(jù)
});
調(diào)用 UseRouting 之前,終結(jié)點(diǎn)始終為 null。
如果找到匹配項(xiàng),則 UseRouting 和 UseEndpoints 之間的終結(jié)點(diǎn)為非 null。
如果找到匹配項(xiàng),則 UseEndpoints 中間件即為終端。稍后會(huì)在本文檔中定義終端中間件。
僅當(dāng)找不到匹配項(xiàng)時(shí)才執(zhí)行 UseEndpoints 后的中間件。
GitHub源碼鏈接:
https://github.com/MINGSON666/Personal-Learning-Library/tree/main/ArchitectTrainingCamp/HelloApi
課程鏈接
.NET云原生架構(gòu)師訓(xùn)練營講什么,怎么講,講多久
