mcpField
O
openapi

openapi

github.com/codewithmukesh开发7913.0k 次安装12 周前更新
代码仓库

关于 openapi

面向 .NET 10 开发者的 OpenAPI 实践指南,介绍使用框架内置的 Microsoft.AspNetCore.OpenApi 生成文档。内容涵盖端点元数据、类型化响应、转换器、多文档和构建时生成。

主要功能

  1. 01基于 TypedResults 生成响应架构
  2. 02端点元数据与转换器
  3. 03多个 OpenAPI 文档
  4. 04集成 XML 文档注释
  5. 05构建时生成规范文件

适用场景

  1. 01为新的 .NET API 编写文档
  2. 02自定义安全方案和架构
  3. 03生成供客户端代码工具使用的规范

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill openapi

安装后在 Claude Code 中输入/openapi确认已加载。

详细介绍

OpenAPI

核心原则

  1. 内置方案,而非 Swashbuckle — .NET 10 将 Microsoft.AspNetCore.OpenApi 作为官方且由框架维护的 OpenAPI 解决方案。Swashbuckle 已从 .NET 9 模板中移除,不再推荐使用。
  2. 由 TypedResults 驱动架构 — TypedResults.Ok<T>() 会自动生成正确的 OpenAPI 响应架构,而 Results.Ok() 不会。始终使用 TypedResults。
  3. 使用转换器,而非变通方案 — 文档、操作和架构转换器可以干净地组合使用。使用它们来配置安全方案、全局响应和架构自定义。
  4. 为每个端点添加元数据 — 为每个端点使用 .WithName()、.WithSummary() 和 .WithTags()。这些元数据会直接写入 OpenAPI 规范,并供客户端生成器使用。

模式

基本设置
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();  // Serves at /openapi/v1.json
}
端点元数据
group.MapPost("/", CreateOrder)
    .WithName("CreateOrder")
    .WithSummary("Create a new order")
    .WithDescription("Creates a new order for the specified customer.")
    .Produces<OrderResponse>(StatusCodes.Status201Created)
    .ProducesValidationProblem()
    .ProducesProblem(StatusCodes.Status500InternalServerError);

使用 TypedResults 时,响应元数据会自动推断:

static async Task<Results<Created<OrderResponse>, ValidationProblem>> CreateOrder(
    CreateOrderRequest request, ISender sender, CancellationToken ct)
{
    var result = await sender.Send(new CreateOrder.Command(request), ct);
    return result.IsSuccess
        ? TypedResults.Created($"/api/orders/{result.Value.Id}", result.Value)
        : TypedResults.ValidationProblem(result.Errors);
}
Bearer 令牌安全方案
builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});

internal sealed class BearerSecuritySchemeTransformer(
    IAuthenticationSchemeProvider authSchemeProvider) : IOpenApiDocumentTransformer
{
    public async Task TransformAsync(OpenApiDocument document,
        OpenApiDocumentTransformerContext context, CancellationToken ct)
    {
        var schemes = await authSchemeProvider.GetAllSchemesAsync();
        if (!schemes.Any(s => s.Name == "Bearer"))
            return;

        document.Components ??= new OpenApiComponents();
        document.Components.SecuritySchemes = new Dictionary<string, IOpenApiSecurityScheme>
        {
            ["Bearer"] = new OpenApiSecurityScheme
            {
                Type = SecuritySchemeType.Http,
                Scheme = "bearer",
                BearerFormat = "JWT",
                In = ParameterLocation.Header
            }
        };

        foreach (var operation in document.Paths.Values.SelectMany(p => p.Operations))
        {
            operation.Value.Security ??= [];
            operation.Value.Security.Add(new OpenApiSecurityRequirement
            {
                [new OpenApiSecuritySchemeReference("Bearer", document)] = []
            });
        }
    }
}
文档信息转换器
builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, ct) =>
    {
        document.Info = new()
        {
            Title = "Checkout API",
            Version = "v1",
            Description = "API for processing orders and payments."
        };
        return Task.CompletedTask;
    });
});
多个 OpenAPI 文档
builder.Services.AddOpenApi("v1");
builder.Services.AddOpenApi("internal", options =>
{
    options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});

// Endpoints choose their document via WithGroupName
app.MapGet("/public", () => "Hello").WithGroupName("v1");
app.MapGet("/admin", () => "Secret").WithGroupName("internal");

未设置 .WithGroupName() 的端点会出现在所有文档中。

XML 文档注释(.NET 10)

在项目文件中启用此功能 — 源生成器会自动提取 <summary>、<param> 和 <response> 标签:

<PropertyGroup>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
/// <summary>Retrieves a project board by ID.</summary>
/// <param name="id">The project board ID.</param>
/// <response code="200">Returns the project board.</response>
/// <response code="404">Board not found.</response>
static async Task<Results<Ok<Board>, NotFound>> GetBoard(int id, AppDbContext db)
{
    var board = await db.Boards.FindAsync(id);
    return board is not null ? TypedResults.Ok(board) : TypedResults.NotFound();
}

编译器不会捕获 lambda 上的 XML 注释。请使用具名方法。

架构转换器
options.AddSchemaTransformer((schema, context, ct) =>
{
    if (context.JsonTypeInfo.Type == typeof(decimal))
    {
        schema.Format = "decimal";
    }
    return Task.CompletedTask;
});
每个端点的操作转换器(.NET 10)
app.MapGet("/old", () => "deprecated")
    .AddOpenApiOperationTransformer((operation, context, ct) =>
    {
        operation.Deprecated = true;
        return Task.CompletedTask;
    });
构建时生成文档
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" Version="*" />
<PropertyGroup>
    <OpenApiDocumentsDirectory>.</OpenApiDocumentsDirectory>
</PropertyGroup>

构建期间,规范文件会生成到输出目录中。

YAML 端点(.NET 10)
app.MapOpenApi("/openapi/{documentName}.yaml");

反模式

新项目不要使用 Swashbuckle
// BAD — removed from .NET 9+ templates, maintenance concerns
builder.Services.AddSwaggerGen();
app.UseSwagger();
app.UseSwaggerUI();

// GOOD — built-in OpenAPI
builder.Services.AddOpenApi();
app.MapOpenApi();
在 .NET 10 中不要使用 WithOpenApi()
// BAD — deprecated, produces ASPDEPR002 warning
app.MapGet("/", () => "hello").WithOpenApi(op => { op.Deprecated = true; return op; });

// GOOD — use per-endpoint operation transformer
app.MapGet("/", () => "hello")
    .AddOpenApiOperationTransformer((op, ctx, ct) =>
    {
        op.Deprecated = true;
        return Task.CompletedTask;
    });
不要使用非类型化结果
// BAD — Results.Ok doesn't contribute to OpenAPI schema
static async Task<IResult> GetOrder(Guid id, AppDbContext db)
{
    var order = await db.Orders.FindAsync(id);
    return order is not null ? Results.Ok(order) : Results.NotFound();
}

// GOOD — TypedResults with union return type
static async Task<Results<Ok<Order>, NotFound>> GetOrder(Guid id, AppDbContext db)
{
    var order = await db.Orders.FindAsync(id);
    return order is not null ? TypedResults.Ok(order) : TypedResults.NotFound();
}
不要省略端点上的 WithName
// BAD — client generators produce poor method names without operationId
group.MapGet("/{id:guid}", GetOrder);

// GOOD — operationId feeds into generated client method names
group.MapGet("/{id:guid}", GetOrder).WithName("GetOrder");
在 .NET 10 中不要使用 OpenApiAny
// BAD — OpenApiAny types removed in Microsoft.OpenApi v2.x
schema.Example = new OpenApiString("2025-01-01");

// GOOD — use JsonNode from System.Text.Json.Nodes
schema.Example = JsonValue.Create("2025-01-01");

决策指南

场景建议
新建 API 项目AddOpenApi() + MapOpenApi()(内置)
API 文档 UIScalar(MapScalarApiReference())
文档中的安全方案使用 IOpenApiDocumentTransformer 的文档转换器
响应文档使用带联合返回类型的 TypedResults
集成 XML 文档<GenerateDocumentationFile>true</GenerateDocumentationFile>
多个 API 版本多次调用 AddOpenApi("v1") + WithGroupName()
客户端代码生成Kiota(Microsoft 推荐)或 NSwag
构建时生成规范Microsoft.Extensions.ApiDescription.Server 包
OpenAPI 版本3.1(.NET 10 中的默认版本);如果使用方需要,可强制使用 3.0
按端点自定义在端点上使用 .AddOpenApiOperationTransformer()

评论

支持 Markdown