mcpField
O
openapi

openapi

github.com/codewithmukesh開発7913.0k 回インストール12 週間前に更新
リポジトリ

openapi について

.NET 10 の開発者向けに、組み込みの Microsoft.AspNetCore.OpenApi を使った OpenAPI 文書の作成方法をまとめたスキルです。エンドポイントメタデータ、型付きレスポンス、トランスフォーマー、複数文書、ビルド時生成を扱います。

主な機能

  1. 01TypedResults によるレスポンススキーマ生成
  2. 02エンドポイントメタデータとトランスフォーマー
  3. 03複数の OpenAPI 文書
  4. 04XML ドキュメントコメントの統合
  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 には、公式のフレームワーク保守による OpenAPI ソリューションとして Microsoft.AspNetCore.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();
}

ラムダ式の 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;
    });
型指定のない Results を使用しない
// 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() を使用

コメント