关于 web-api
用于设计、审查和维护基于控制器的 ASP.NET Core API 的技能。它强调明确的 HTTP 契约与策略,并帮助团队判断何时应使用控制器而非 Minimal APIs。
主要功能
- 01控制器结构与精简操作
- 02明确模型绑定和 DTO 边界
- 03验证与一致的错误响应
- 04API 版本控制与异常处理
适用场景
- 01审查现有 API 控制器和筛选器
- 02构建 HTTP 行为可预测的控制器 API
- 03减少控制器中的业务逻辑和持久化代码
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ managedcode/ dotnet-skills --skill web-api安装后在 Claude Code 中输入/web-api确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
manifest.jsonreferences/anti-patterns.mdpatterns.md详细介绍
ASP.NET Core Web API
触发条件
- 在 ASP.NET Core 中开发基于控制器的 API
- 需要控制器专属的扩展能力或约定
- 迁移或审查现有 API 控制器和筛选器
工作流
- 只有在 API 需要以控制器为中心的功能时才使用控制器,不要仅仅因为旧模板如此就采用控制器。
- 保持控制器精简:将 HTTP 相关职责交给应用服务或处理程序,避免在操作方法中直接嵌入数据访问和业务规则。
- 明确 DTO 边界,显式进行验证,并确保 HTTP 状态行为可预测。
- 在控制器和端点两个层面检查身份验证与授权,避免 API 表面出现意外的不一致。
- 有意识地处理 OpenAPI 生成、版本控制和错误契约行为,而不是任其偶然形成。
- 对于新的简单 API,使用
minimal-apis,而不是习惯性地默认使用控制器。
当前上游说明
dotnet/aspnetcorev10.0.11属于维护版本,并将 OpenAPI 堆栈更新至2.7.5;基于控制器的 API 指南仍取决于约定、高级模型绑定、OData、JsonPatch 和现有筛选器。升级后,重新运行生成文档和客户端契约检查。- 2026 年 8 月的
aspnetcore-10.0概述仍将控制器 API 与 Minimal APIs 并列,而非用后者取代前者。更改公共 API 契约前,请参考专门的路由、OpenAPI、身份验证和托管页面。
交付内容
- 具有明确契约和策略的控制器 API
- 精简的控制器
- 针对关键 API 行为的测试或冒烟检查
验证
- 确实有理由使用控制器功能
- 操作方法不会隐藏业务逻辑和持久化细节
- 各端点的 HTTP 语义保持可预测
控制器结构
使用主构造函数(C# 12+)进行依赖注入,并让控制器专注于 HTTP 相关职责:
[ApiController]
[Route("api/[controller]")]
public class OrdersController(
IOrderService orderService,
ILogger<OrdersController> logger) : ControllerBase
{
[HttpGet("{id:guid}")]
[ProducesResponseType<OrderDto>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(Guid id, CancellationToken ct)
{
var order = await orderService.GetByIdAsync(id, ct);
return order is null ? NotFound() : Ok(order);
}
[HttpPost]
[ProducesResponseType<OrderDto>(StatusCodes.Status201Created)]
[ProducesResponseType<ValidationProblemDetails>(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Create(CreateOrderRequest request, CancellationToken ct)
{
var order = await orderService.CreateAsync(request, ct);
return CreatedAtAction(nameof(GetById), new { id = order.Id }, order);
}
}
模型绑定
显式声明绑定来源以提高清晰度:
[HttpGet("{id:guid}")]
public async Task<IActionResult> GetWithOptions(
[FromRoute] Guid id,
[FromQuery] bool includeDeleted = false,
[FromHeader(Name = "X-Correlation-Id")] string? correlationId = null,
CancellationToken ct = default)
{
// Route: id, Query: includeDeleted, Header: X-Correlation-Id
}
为请求 DTO 使用带有必需成员的 record 类型:
public record CreateProductRequest
{
public required string Name { get; init; }
public required decimal Price { get; init; }
public string? Description { get; init; }
public IReadOnlyList<string> Tags { get; init; } = [];
}
验证
对于复杂的验证规则,优先使用 FluentValidation:
public class CreateOrderRequestValidator : AbstractValidator<CreateOrderRequest>
{
public CreateOrderRequestValidator(IProductRepository products)
{
RuleFor(x => x.CustomerId)
.NotEmpty()
.WithMessage("Customer ID is required");
RuleFor(x => x.Items)
.NotEmpty()
.WithMessage("Order must contain at least one item");
RuleForEach(x => x.Items).ChildRules(item =>
{
item.RuleFor(i => i.ProductId)
.NotEmpty()
.MustAsync(async (id, ct) => await products.ExistsAsync(id, ct))
.WithMessage("Product does not exist");
item.RuleFor(i => i.Quantity)
.GreaterThan(0)
.LessThanOrEqualTo(100);
});
}
}
配置一致的 Problem Details 响应:
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var problemDetails = new ValidationProblemDetails(context.ModelState)
{
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
Title = "One or more validation errors occurred.",
Status = StatusCodes.Status400BadRequest,
Instance = context.HttpContext.Request.Path
};
return new BadRequestObjectResult(problemDetails);
};
});
API 版本控制
配置 URL 路径版本控制:
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
})
.AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
[ApiController]
[Route("api/v{version:apiVersion}/products")]
[ApiVersion("1.0")]
public class ProductsV1Controller(IProductService productService) : ControllerBase
{
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id, CancellationToken ct)
{
var product = await productService.GetAsync(id, ct);
return Ok(product);
}
}
异常处理
使用全局异常处理程序,确保错误响应一致:
public class GlobalExceptionHandler(
ILogger<GlobalExceptionHandler> logger) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
logger.LogError(exception, "Unhandled exception occurred");
var problemDetails = exception switch
{
ValidationException validationEx => new ProblemDetails
{
Status = StatusCodes.Status400BadRequest,
Title = "Validation Error",
Detail = validationEx.Message
},
NotFoundException notFoundEx => new ProblemDetails
{
Status = StatusCodes.Status404NotFound,
Title = "Resource Not Found",
Detail = notFoundEx.Message
},
_ => new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError,
Title = "Internal Server Error"
}
};
problemDetails.Extensions["traceId"] = httpContext.TraceIdentifier;
httpContext.Response.StatusCode = problemDetails.Status ?? 500;
await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
return true;
}
}
参考资料
- patterns.md - 控制器模式、模型绑定、验证、版本控制、响应处理和筛选器模式
- anti-patterns.md - 应避免的常见 API 错误,包括控制器臃肿、错误不一致、缺少取消令牌和 HTTP 语义不当
评论
支持 Markdown