openapi について
.NET 10 の開発者向けに、組み込みの Microsoft.AspNetCore.OpenApi を使った OpenAPI 文書の作成方法をまとめたスキルです。エンドポイントメタデータ、型付きレスポンス、トランスフォーマー、複数文書、ビルド時生成を扱います。
主な機能
- 01TypedResults によるレスポンススキーマ生成
- 02エンドポイントメタデータとトランスフォーマー
- 03複数の OpenAPI 文書
- 04XML ドキュメントコメントの統合
- 05ビルド時の仕様ファイル生成
活用シーン
- 01新しい .NET API の文書化
- 02セキュリティ設定とスキーマのカスタマイズ
- 03クライアントコード生成ツール向け仕様の生成
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ codewithmukesh/ dotnet-claude-kit --skill openapiインストール後、Claude Code で/openapiを入力して読み込まれたか確認します。
詳細
OpenAPI
基本原則
- Swashbuckle ではなく組み込み機能を使用する — .NET 10 には、公式のフレームワーク保守による OpenAPI ソリューションとして
Microsoft.AspNetCore.OpenApiが含まれています。Swashbuckle は .NET 9 でテンプレートから削除され、推奨されなくなりました。 - スキーマは TypedResults によって決まる —
TypedResults.Ok<T>()は正しい OpenAPI レスポンススキーマを自動生成しますが、Results.Ok()は生成しません。必ずTypedResultsを使用してください。 - 回避策ではなくトランスフォーマーを使用する — ドキュメント、オペレーション、スキーマの各トランスフォーマーは、きれいに組み合わせられます。セキュリティスキーム、グローバルレスポンス、スキーマのカスタマイズには、これらを使用してください。
- すべてのエンドポイントにメタデータを設定する — すべてのエンドポイントで
.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 ドキュメント UI | Scalar(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 対応