基本 API 應用程式中的路由處理常式
注意
這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支援原則。 如需目前版本,請參閱本文的 .NET 8 版本。
已設定的 WebApplication
支援 Map{Verb}
和 MapMethods,其中 {Verb}
是 Pascal 大小寫的 HTTP 方法,例如 Get
、Post
、Put
或 Delete
:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");
app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" },
() => "This is an options or head request ");
app.Run();
傳遞給這些方法的 Delegate 引數稱為「路由處理常式」。
路由處理常式
路由處理常式是路由相符時所執行的方法。 路由處理常式可以是 Lambda 運算式、本機函式、執行個體方法或靜態方法。 路由處理常式可以是同步或非同步。
Lambda 運算式
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/inline", () => "This is an inline lambda");
var handler = () => "This is a lambda variable";
app.MapGet("/", handler);
app.Run();
本機函式
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
執行個體方法
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var handler = new HelloHandler();
app.MapGet("/", handler.Hello);
app.Run();
class HelloHandler
{
public string Hello()
{
return "Hello Instance method";
}
}
靜態方法
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", HelloHandler.Hello);
app.Run();
class HelloHandler
{
public static string Hello()
{
return "Hello static method";
}
}
定義於 Program.cs
外部的端點
基本 API 不一定位於 Program.cs
中。
Program.cs
using MinAPISeparateFile;
var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();
TodoEndpoints.Map(app);
app.Run();
TodoEndpoints.cs
namespace MinAPISeparateFile;
public static class TodoEndpoints
{
public static void Map(WebApplication app)
{
app.MapGet("/", async context =>
{
// Get all todo items
await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
});
app.MapGet("/{id}", async context =>
{
// Get one todo item
await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
});
}
}
另請參閱本文稍後的路由群組。
具名端點和連結產生
端點可以指定名稱,以產生端點的 URL。 使用具名端點可避免在應用程式中使用硬式程式碼路徑:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => "Hello named route")
.WithName("hi");
app.MapGet("/", (LinkGenerator linker) =>
$"The link to the hello route is {linker.GetPathByName("hi", values: null)}");
app.Run();
上述程式碼會從 /
端點顯示 The link to the hello route is /hello
。
注意:端點名稱區分大小寫。
端點名稱:
- 必須是全域唯一的。
- 啟用 OpenAPI 支援時,會當做 OpenAPI 作業識別碼使用。 如需詳細資訊,請參閱 OpenAPI。
路由參數
路由參數可以擷取做為路由模式定義的一部分:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/users/{userId}/books/{bookId}",
(int userId, int bookId) => $"The user id is {userId} and book id is {bookId}");
app.Run();
上述程式碼會從 URI /users/3/books/7
傳回 The user id is 3 and book id is 7
。
路由處理常式可以宣告要擷取的參數。 當要求成為宣告要擷取參數的路由時,將剖析參數並傳遞至處理常式。 這可讓您輕鬆地以類型安全的方式擷取值。 在上述程式碼中,userId
和 bookId
都是 int
。
在上述程式碼中,如果任一路由值無法轉換成 int
,則會擲回例外狀況。 GET 要求 /users/hello/books/3
會擲回下列例外狀況:
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
萬用字元及攔截所有路由
下列項目會從 `/posts/hello' 端點攔截傳回 Routing to hello
的所有路由:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
路由條件約束
路由條件約束會限制路由的比對行為。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));
app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t => t.Text.Contains(text));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post {slug}");
app.Run();
下表示範上述路由範本及其行為:
路由範本 | 範例比對 URI |
---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
如需詳細資訊,請參閱 ASP.NET Core 中的路由中路由條件約束參考。
路由群組
MapGroup 擴充方法可協助組織具有常見前置詞的端點群組。 其可減少重複的程式碼,並允許使用單一呼叫方法 (例如 RequireAuthorization 和 WithMetadata,其可新增端點中繼資料) 來自訂整個端點群組。
例如,下列程式碼會建立兩個類似的端點群組:
app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");
app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();
EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;
foreach (var argument in factoryContext.MethodInfo.GetParameters())
{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}
// Skip filter if the method doesn't have a TodoDb parameter.
if (dbContextIndex < 0)
{
return next;
}
return async invocationContext =>
{
var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
dbContext.IsPrivate = true;
try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);
return group;
}
在此案例中,您可以在 201 Created
結果中使用 Location
標頭的相對位址:
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
第一組端點只會比對以 /public/todos
為前置詞且不需要任何驗證即可存取的要求。 第二組端點只會比對以 /private/todos
為前置詞且需要驗證的要求。
QueryPrivateTodos
端點篩選處理站是本機函式,可修改路由處理常式的 TodoDb
參數,以允許存取及儲存私人代辦事項資料。
路由群組也支援具有路由參數和條件約束的巢狀群組和複雜前置詞模式。 在下列範例中,對應至 user
群組的路由處理常式可以擷取外部群組前置詞中定義的 {org}
和 {group}
路由參數。
前置詞也可能是空的。 這適用於將端點中繼資料或篩選條件新增至一組端點,而不需要變更路由模式。
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
將篩選條件或中繼資料新增至群組的行為,與將篩選條件或中繼資料個別新增至每個端點,再新增任何可能已新增至內部群組或特定端點的額外篩選條件或中繼資料的方式相同。
var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");
inner.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/inner group filter");
return next(context);
});
outer.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/outer group filter");
return next(context);
});
inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("MapGet filter");
return next(context);
});
在上述範例中,即使已新增第二個要求,外部篩選條件也會在內部篩選條件之前記錄傳入要求。 由於篩選條件已套用至不同的群組,因此彼此相對的新增順序並不重要。 如果套用至相同的群組或特定端點,則篩選條件的新增順序就很重要。
對 /outer/inner/
的要求會記錄下列內容:
/outer group filter
/inner group filter
MapGet filter
參數繫結
基本 API 應用程式中的參數繫結會詳細說明路由處理常式參數填入方式的規則。
回覆
在基本 API 應用程式中建立回應會詳細說明從路由處理常式傳回的值如何轉換成回應。