路由组、参数与验证
使用 MapGroup 组织端点,绑定路由、查询和请求正文参数,并使用数据注解进行验证。
路由组、参数与验证 是 CoddyKit 上的免费 C# Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
用于组织的路由组
MapGroup()(在 .NET 7 中引入)可以将相关端点归入同一个公共前缀下,并应用共享配置(中间件、身份验证策略、标签),而不必在每个端点上重复配置。
创建路由组
调用 app.MapGroup("/api/products"),然后在返回的组上映射端点。该前缀会自动添加到所有路由之前。
var products = app.MapGroup("/api/products").WithTags("Products");
products.MapGet("/", GetAll);
products.MapGet("/{id}", GetById);
products.MapPost("/", Create);
products.MapPut("/{id}", Update);
products.MapDelete("/{id}", Delete);
// Routes: /api/products, /api/products/{id}为组应用身份验证
在组上使用 RequireAuthorization(),即可一次性保护所有端点。单个端点仍可使用 AllowAnonymous() 覆盖此设置。
var adminGroup = app.MapGroup("/admin")
.RequireAuthorization("AdminPolicy")
.WithTags("Admin");
adminGroup.MapGet("/users", GetAllUsers);
adminGroup.MapDelete("/users/{id}", DeleteUser);
// This one inside the group opts out
adminGroup.MapGet("/status", () => "OK").AllowAnonymous();路由参数约束
使用类型模式约束路由参数。路由器会拒绝不匹配的请求,并自动返回 404。
// Only matches numeric IDs
app.MapGet("/products/{id:int}", (int id) => id);
// GUID constraint
app.MapGet("/orders/{orderId:guid}", (Guid orderId) => orderId);
// Length constraint
app.MapGet("/codes/{code:length(6)}", (string code) => code);
// Min value
app.MapGet("/page/{page:min(1)}", (int page) => page);从不同来源绑定
使用特性将参数绑定到不同来源。默认情况下,简单类型来自路由或查询,复杂类型来自请求正文。
app.MapPost("/search", (
[FromQuery] string q,
[FromQuery] int page,
[FromHeader(Name = "X-Api-Version")] string version,
[FromBody] SearchFilters filters,
AppDbContext db) =>
{
// q and page from query string
// version from request header
// filters from JSON body
return Results.Ok();
});使用 TryParse 自定义参数绑定
对于用作路由或查询参数的自定义类型,请实现静态 TryParse 方法。框架会自动调用该方法来解析字符串值。
public record ProductFilter(string? Category, decimal? MinPrice)
{
public static bool TryParse(string value, out ProductFilter result)
{
var parts = value.Split(':');
result = new ProductFilter(
parts.Length > 0 ? parts[0] : null,
parts.Length > 1 && decimal.TryParse(parts[1], out var p) ? p : null);
return true;
}
}
// Usage: GET /products?filter=Electronics:50
app.MapGet("/products", ([FromQuery] ProductFilter filter) => filter);数据注解验证
为请求 DTO 添加数据注解特性。在最小 API 中,请使用 IValidatableObject 接口或验证筛选器来触发验证。
using System.ComponentModel.DataAnnotations;
record CreateProductRequest
{
[Required, MaxLength(200)]
public string Name { get; init; } = "";
[Range(0.01, 999999)]
public decimal Price { get; init; }
[Range(0, int.MaxValue)]
public int Stock { get; init; }
}使用端点筛选器进行验证
端点筛选器会在处理程序运行前后执行。您可以使用它们添加模型验证,在无效请求到达业务逻辑之前将其拒绝。
app.MapPost("/products", CreateProduct)
.AddEndpointFilter(async (ctx, next) =>
{
var req = ctx.GetArgument<CreateProductRequest>(0);
var errors = new List<ValidationResult>();
if (!Validator.TryValidateObject(req,
new ValidationContext(req), errors, true))
{
return Results.ValidationProblem(
errors.ToDictionary(e => e.MemberNames.First(),
e => new[] { e.ErrorMessage! }));
}
return await next(ctx);
});FluentValidation 集成
对于复杂的验证规则,请将 FluentValidation 与 SharpGrip.FluentValidation.AutoValidation.Endpoints 包结合使用,以便在最小 API 中自动进行验证。
public class CreateProductValidator : AbstractValidator<CreateProductRequest>
{
public CreateProductValidator()
{
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
RuleFor(x => x.Price).GreaterThan(0);
RuleFor(x => x.Stock).GreaterThanOrEqualTo(0);
}
}
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddValidatorsFromAssemblyContaining<CreateProductValidator>();嵌套路由组
路由组可以嵌套,用于建模层次化资源,例如包含订单项的订单。
var api = app.MapGroup("/api").RequireAuthorization();
var orders = api.MapGroup("/orders");
orders.MapGet("/", GetAllOrders);
orders.MapGet("/{id:int}", GetOrder);
// Nested: /api/orders/{orderId}/lines
var lines = orders.MapGroup("/{orderId:int}/lines");
lines.MapGet("/", GetOrderLines);
lines.MapPost("/", AddOrderLine);
lines.MapDelete("/{id}", RemoveOrderLine);真实案例:版本化 API 组
按 API 版本对端点分组,可以在不重复身份验证或标签配置的情况下支持并行版本控制。
var v1 = app.MapGroup("/api/v1")
.RequireAuthorization()
.WithTags("v1");
var v2 = app.MapGroup("/api/v2")
.RequireAuthorization()
.WithTags("v2");
v1.MapGet("/products", GetProductsV1);
v2.MapGet("/products", GetProductsV2); // richer response快速检查
MapGroup() 在最小 API 中的作用是什么?
回顾:路由组、参数与验证
要点:
MapGroup():在多个端点之间共享前缀和配置- 路由约束(
:int、:guid、:min)会自动拒绝不匹配的路由 - 使用
[FromQuery]、[FromHeader]、[FromBody]显式指定绑定源 - 在自定义类型上实现
TryParse,以自动绑定路由或查询参数 - 端点筛选器支持验证等横切关注点
常见问题解答
「路由组、参数与验证」课时是免费的吗?
是的 — 「路由组、参数与验证」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「路由组、参数与验证」这节课中我会学到什么?
使用 MapGroup 组织端点,绑定路由、查询和请求正文参数,并使用数据注解进行验证。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「路由组、参数与验证」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。