0Pricing
C# Academy · 课时

路由组、参数与验证

使用 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 创建第一个 Minimal API
  2. 路由组、参数与验证
  3. Minimal API 中间件与筛选器
  4. OpenAPI、版本控制与部署
← 返回 C# Academy