后端与接口
在 .NET 10 中构建 Minimal API…
适用于 .NET 10 Minimal API 的新建 HTTP API、路由分组、OpenAPI 文档、参数绑定、验证、限流和输出缓存配置。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在 .NET 10 中构建 Minima特定
解决思路
- 默认使用 Minimal APIs 构建 HTTP 端点;仅在迁移遗留代码时使用 controllers;Minimal APIs 更轻、更快,并可组合任意架构风格。
- 用 MapGroup 分组端点;绝不要把单个 MapGet/MapPost 调用散落在 Program.cs;把相关端点归到同一组。
- 使用 TypedResults 以获得 OpenAPI 支持;TypedResults.Ok(value) 提供编译期类型安全和正确的 OpenAPI 文档;Results.Ok(value) 不具备。
- 用元数据代替注释:使用 .WithName()、.WithTags()、.WithSummary() 记录端点;这些元数据会进入 OpenAPI spec。
- 端点组自动发现是必需模式:每个端点组放在自己的文件并实现 IEndpointGroup;在 Program.cs 只调用一次 app.MapEndpoints() 自动发现并注册所有组;新增端点组时 Program.cs 永不改变。
- 定义 Extensions/IEndpointGroup.cs:public interface IEndpointGroup { void Map(IEndpointRouteBuilder app); }。
- 定义 Extensions/EndpointExtensions.cs 中的 MapEndpoints 扩展:扫描 typeof(Program).Assembly 中可赋值给 IEndpointGroup、非接口、非抽象的类型,用 Activator.CreateInstance 创建实例,Cast<IEndpointGroup>,对每个 group 调用 group.Map(app),返回 app。
- Program.cs 模式:var app = builder.Build(); app.MapEndpoints(); app.Run(); 添加新端点时该文件不变。
- 每个端点组一个文件,例如 Features/Orders/OrderEndpoints.cs:public sealed class OrderEndpoints : IEndpointGroup,在 Map 方法中 var group = app.MapGroup 传入 /api/orders 并链式 .WithTags 传入 Orders。
- 在组内定义路由并配置元数据和响应:group.MapPost 传入 / 和 CreateOrder,链式 .WithName 传入 CreateOrder、.WithSummary 传入 Create a new order、.Produces<OrderResponse>(StatusCodes.Status201Created)、.ProducesValidationProblem()、.RequireAuthorization();group.MapGet 传入 /{id:guid} 和 GetOrder,链式 .WithName 传入 GetOrder、.Produces<OrderResponse>()、.ProducesProblem(StatusCodes.Status404NotFound);group.MapGet 传入 / 和 ListOrders,链式 .WithName 传入 ListOrders、.Produces<PagedList<OrderResponse>>()。
- 处理器使用 union 返回类型 Results<...>;CreateOrder 成功时返回 TypedResults.Created 传入 /api/orders/{result.Value.Id} 和 result.Value,失败时返回 TypedResults.ValidationProblem(result.Errors);GetOrder 成功时返回 TypedResults.Ok(result.Value),失败时返回 TypedResults.NotFound();ListOrders 返回 TypedResults.Ok(result)。
- 用 TypedResults 实现类型安全响应:处理器签名如 Results<Ok<Product>, NotFound, ValidationProblem>,返回 TypedResults.Ok(product) 或 TypedResults.NotFound(),获得编译期保证和自动 OpenAPI schema 生成。
- 参数绑定:.NET 10 minimal APIs 自动从 route、query、header、body 和 DI 绑定参数。
- 路由参数绑定:app.MapGet 传入 /orders/{id:guid} 和 (Guid id) => ...。
- 查询参数绑定:app.MapGet 传入 /orders 和 (int page, int? pageSize, string? status) => ...;nullable 表示可选。
- 复杂查询参数用 [AsParameters]:public record ListOrdersQuery(int Page = 1, int PageSize = 20, string? Status = null); app.MapGet 传入 /orders 和 ([AsParameters] ListOrdersQuery query) => ...。
- 请求头绑定:使用 [FromHeader(Name = X-Correlation-Id)] string? correlationId。
- DI 服务自动解析,无需 attribute:app.MapPost 传入 /orders 和 (CreateOrderRequest request, ISender sender) => ...。
- 端点过滤器是 minimal API 中 action filters 的等价物;用于验证、日志和幂等检查等横切关注点。
- 使用 canonical ValidationFilter<TRequest>(FluentValidation,从 DI 解析 validator,未注册时优雅跳过);它位于 error-handling skill,不要在每个项目重复实现。对变更端点应用:group.MapPost 传入 / 和 CreateOrder 后 .AddEndpointFilter<ValidationFilter<CreateOrderRequest>>();对组应用:group.AddEndpointFilter<LoggingFilter>()。
- OpenAPI/Swagger 配置:.NET 10 有内置 OpenAPI 支持;使用它而不是 Swashbuckle。
- Program.cs 只做服务注册和最小接线:builder.Services.AddOpenApi(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } app.MapEndpoints();。
- 用端点元数据丰富 OpenAPI spec:链式 .WithName 传入 CreateOrder、.WithSummary 传入 Create a new order、.WithDescription 传入 Creates a new order for the specified customer with the given line items.、.Produces<OrderResponse>(StatusCodes.Status201Created)、.ProducesValidationProblem()、.ProducesProblem(StatusCodes.Status500InternalServerError)。
- 限流配置:builder.Services.AddRateLimiter(options => { options.AddFixedWindowLimiter 传入 api 和 opt => { opt.PermitLimit = 100; opt.Window = TimeSpan.FromMinutes(1); }; });。
- 在 IEndpointGroup.Map 方法内应用限流:var group = app.MapGroup 传入 /api/orders 并链式 .WithTags 传入 Orders、.RequireRateLimiting 传入 api。
- 输出缓存配置:builder.Services.AddOutputCache(options => { options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromMinutes(5))); options.AddPolicy 传入 ByIdCache 和 builder => builder.Expire(TimeSpan.FromMinutes(10)).SetVaryByRouteValue 传入 id; });。
- 对端点应用输出缓存:group.MapGet 传入 /{id:guid} 和 GetOrder 后链式 .CacheOutput 传入 ByIdCache。
- 避免把端点放进 Program.cs:不要散落 MapGet/MapPost,也不要手动 MapGroup 调用让 Program.cs 随每个功能增长;用 app.MapEndpoints() 自动发现。
- 避免无类型结果:不要返回 IResult 并用 Results.Ok/Results.NotFound,因为它不贡献 OpenAPI schema;改用 TypedResults 和显式 union 类型 Results<Ok<Order>, NotFound>。
- 避免直接返回领域实体:不要泄露内部结构、让 API 无法独立演进;改为投影到 response DTO,例如 Select 生成 new OrderResponse(o.Id, o.Total, o.CreatedAt),再返回 TypedResults.Ok 或 TypedResults.NotFound。
- 决策指南:新建 HTTP API 使用每个 feature 一个 IEndpointGroup 加 app.MapEndpoints() 自动发现。
- 决策指南:现有 MVC 项目保留 controllers,增量迁移。
- 决策指南:OpenAPI 文档使用 TypedResults 加 .WithName() 加 .WithSummary()。
- 决策指南:请求验证使用 FluentValidation 的 endpoint filter。
- 决策指南:认证/授权在 group 或 endpoint 上用 .RequireAuthorization 传入 PolicyName。
- 决策指南:限流用 AddRateLimiter 加 .RequireRateLimiting()。
- 决策指南:响应缓存用 AddOutputCache 加 .CacheOutput()。
- 决策指南:复杂模型绑定用 record 类型配合 [AsParameters]。
适用边界 · 注意事项
- 现有 MVC 项目不适用全量重写:保留 controllers,增量迁移,不要因为本技能而重写控制器。
- 无法使用 .NET 10 minimal APIs 或内置 OpenAPI 的环境不适用;不要用 Swashbuckle 替代内置 OpenAPI。
- 一次性脚本或极简单文件 API 不适用:不必引入 IEndpointGroup 自动发现、端点过滤器和完整 OpenAPI 元数据。