「Swagger(Swashbuckle)を入れずに、ASP.NET Core 標準だけで“使える”APIドキュメントを用意したい」。そんなときに役立つのが、.NET の公式パッケージ Microsoft.AspNetCore.OpenApi です。本記事は、OpenAPI 3.0/3.1 の仕様書(JSON/YAML)を自動生成し、好みのUIで閲覧・試用できるまでを、現場投入できる具体性で徹底解説します。
ASP.NET で Swagger 以外の API ドキュメントを作る全体像
まず前提を整理します。ASP.NET Core では、Microsoft.AspNetCore.OpenApi を使うことで、アプリが持つエンドポイント定義(Minimal API / Controller)から OpenAPI 仕様(openapi.json / openapi.yaml) を自動生成できます。ここで生成されるのは「仕様書ファイル」であり、いわゆる見栄えのする UI(Swagger UI 等)は同梱されません。しかし、それで十分です。仕様は標準で賄い、UI は用途に合う軽量なビューア(Redoc、Stoplight Elements、Postman、Slate など)を選べば、より柔軟でメンテナンスしやすい構成になります。
なぜ「Swagger なし」戦略が有効なのか
- 依存関係が最小:標準パッケージのみ。脆弱性対応や互換性調整が軽くなります。
- 最新仕様に追随:OpenAPI 3.1 までの仕様書を自動生成できます。
- UI を自由選択:読み物重視、試用重視、社内公開限定など、要件に応じてベストなビューアを選べます。
- 運用の分離:API 本体とドキュメント表示を分けられるため、ビルド/デプロイ戦略を柔軟に組めます。
最小構成:5分で OpenAPI を出す
パッケージの追加
dotnet add package Microsoft.AspNetCore.OpenApi
Program.cs の設定(最小)
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http;
var builder = WebApplication.CreateBuilder(args);
// OpenAPI(仕様書)生成を有効化
builder.Services.AddOpenApi();
var app = builder.Build();
// サンプルのエンドポイント(Minimal API)
app.MapGet("/weather", () =>
{
var data = new[]
{
new WeatherForecast(DateOnly.FromDateTime(DateTime.Now), 18, "Cloudy")
};
return Results.Ok(data);
})
.WithName("GetWeather")
.WithSummary("サンプルの天気情報を取得します")
.WithDescription("都市や日付の指定は不要な最小例です")
.WithTags("Weather")
.Produces(StatusCodes.Status200OK)
.WithOpenApi(); // このエンドポイントを仕様書に含める
// 仕様書の公開(JSON / YAML のどちらでもOK)
app.MapOpenApi("/openapi.json");
// YAMLで欲しい場合は次行を追加(運用方針に合わせて選択)
app.MapOpenApi("/openapi.yaml");
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string Summary);
これでアプリ起動後、/openapi.json(または /openapi.yaml)にアクセスすれば、OpenAPI 仕様書を取得できます。
UI は後付けで自由に選ぶ(Swagger UI 以外の選択肢)
生成されるのはあくまで仕様書(JSON/YAML)です。ドキュメントとして見やすく、かつ試せる形にしたい場合は、UI を別途用意します。用途別に代表例をまとめると次の通りです。
| ツール | 特徴 | 使い方のポイント | 向いている用途 |
|---|---|---|---|
| Redoc | 読みやすい 3 ペイン構成。カスタマイズ性が高い。 | 静的 HTML を 1 枚生成し、静的ホスティングで配信可能。 | 公開ドキュメント、SaaS の API ポータル |
| Stoplight Elements | インタラクティブな試用 UI とモック機能。 | HTML に Web コンポーネントを埋め込み、openapi.json を読み込む。 | 社内検証、開発者ポータル、デザイン駆動開発 |
| Postman | 閲覧+テスト+コレクション共有が一体。 | OpenAPI をインポートしチームワークスペースで共有。 | QA、結合テスト、クライアント開発 |
| Slate | 読み物型の静的サイト。Markdown ベース。 | API を概念から説明するのに向く。コード例の多いガイドに最適。 | 外部公開の“ドキュメント+ガイド”サイト |
ポイントは、仕様と UI を分離することです。OpenAPI さえ出ていれば、UI は開発フェーズや公開範囲に合わせて差し替えられます。
「Swagger UI 非依存」でも実務で困らない理由
- Try it out(試し打ち)は、Stoplight Elements や Postman で代替可能。
- 静的公開は、Redoc/Slate の 1 ファイル配信がシンプル。
- モックサーバーは、Stoplight/Prism、または Postman の Mock を活用可能(運用は別プロセスで)。
- SDK 生成は、OpenAPI から一般的な生成ツールで対応(CI に組み込む)。
現場で役立つ具体テクニック
エンドポイントにメタデータを付ける
OpenAPI の品質はメタデータで決まります。WithSummary、WithDescription、WithTags、Produces を積極的に使いましょう。
app.MapPost("/weather/{city}", (string city, WeatherRequest req) =>
{
// 何らかの処理…
return Results.Created($"/weather/{city}", new { City = city, Ok = true });
})
.WithName("CreateWeatherRequest")
.WithSummary("天気の申請を登録")
.WithDescription("指定都市に対する天気関連の申請を登録します。")
.WithTags("Weather")
.Produces(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
.WithOpenApi();
スキーマ(モデル)を読みやすくする Tips
- record / class のプロパティ名は C# の PascalCase のままでOK。クライアント期待に合わせる場合は、JSON シリアライザ設定で既定の命名規則(例:camelCase)を指定します。
- Enum は説明コメントを加えるとクライアント実装時の迷いを減らせます。
Results<T1,T2,...>で戻り型を厳密にすると、レスポンスの型とステータスが仕様にも正確に反映されます。
app.MapGet("/orders/{id:int}", (int id) =>
{
return id switch
{
<= 0 => Results.BadRequest(new ProblemDetails { Title = "Invalid id" }),
42 => Results.NotFound(),
_ => Results.Ok(new OrderDto(id, "PAID"))
};
})
.WithName("GetOrder")
.WithSummary("注文の取得")
.WithDescription("注文IDを指定して詳細を返します。")
.WithTags("Orders")
.Produces<OrderDto>(StatusCodes.Status200OK)
.ProducesProblem(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status404NotFound)
.WithOpenApi();
public record OrderDto(int Id, string Status);
JSON と YAML の併用
利用者によって好みは分かれます。MapOpenApi を複数回呼び分ければ、JSON と YAML を両方公開できます。
// JSON / YAML を併用公開する例
app.MapOpenApi("/openapi.json");
app.MapOpenApi("/openapi.yaml");
本番公開のセキュリティ
仕様書の公開範囲は必ず絞りましょう。認証ポリシーや IP 制限での保護が有効です。
// 例:ドキュメントの閲覧を特定ポリシーに限定
app.UseAuthentication();
app.UseAuthorization();
app.MapOpenApi("/openapi.json").RequireAuthorization("DocsViewer");
API 自体は公開するが仕様は社内だけ、という運用は珍しくありません。エッジ(CDN/リバースプロキシ)側でのパスベース制御も合わせると堅牢になります。
ステージングやバージョン別に分ける
ルートグループやタグを用いて、ステージング(stg)と本番(prod)、あるいは v1 と v2 のエンドポイントを構造的に分離しておくと、仕様書の読み手が混乱しません。ルーティング構成とタグ付けの一貫性が鍵です。
CD/CI に組み込んで「常に最新」を保証する
最小構成の自動生成手順(例)
- ビルド完了後にアプリを一時起動(テストポート)。
/openapi.jsonを HTTP 経由で取得し、アーティファクトとして保存。- 必要に応じて YAML 変換や整形を実施。
- 静的ホスティング(例:社内 Web、オブジェクトストレージ)へ配置。
# 疑似的な CI ステップ例
dotnet publish -c Release
# 別ターミナル(またはジョブ)でアプリ起動(例:ポート 5009)
# 取得
curl http://localhost:5009/openapi.json -o openapi.json
curl http://localhost:5009/openapi.yaml -o openapi.yaml
API と仕様の差分を常にゼロに保つことが、クライアント障害の未然防止につながります。
ビューア統合の実装ヒント
Redoc(静的 HTML 配信)
- 単一 HTML を用意し、
openapi.jsonのパスを差し込む。 - CDN またはバンドルしたスクリプトを読み込む(環境に合わせて選択)。
- 静的サイトとして配置(Git-based、オブジェクトストレージ等)。
<!-- redoc.html の例(CDN の URL は運用に合わせて設定) -->
<!doctype html>
<html>
<head><meta charset="utf-8"><title>API Docs</title></head>
<body>
<redoc spec-url="/openapi.json"></redoc>
<script src="<CDNのスクリプトURL>"></script>
</body>
</html>
Stoplight Elements(試用&モック志向)
<!-- elements.html -->
<!doctype html>
<html>
<head><meta charset="utf-8"><title>API Docs</title></head>
<body>
<elements-api
apiDescriptionUrl="/openapi.json"
router="hash">
</elements-api>
<script src="<Elements のスクリプトURL>"></script>
<link rel="stylesheet" href="<Elements のCSS URL>" />
</body>
</html>
Postman(テストと共有を一体化)
- ワークスペースに
openapi.jsonをインポート。 - コレクションとして保存し、環境変数にベース URL / トークンを設定。
- テストスクリプトを用意して回帰テストに流用。
トラブルシューティング
| 症状 | 原因の典型 | 対処 |
|---|---|---|
/openapi.json が 404 | MapOpenApi を呼んでいない/パス相違 | 公開パスを確認。環境によってパスベースのリバースプロキシにも注意。 |
| エンドポイントが仕様に出てこない | WithOpenApi() を付与していない/非公開のルートグループに含めた | エンドポイント側に WithOpenApi() を付与。グループの構造も見直し。 |
| レスポンス型が曖昧 | IResult を無限定で返している | Results<...> で戻り型を限定し、Produces で明示。 |
| JSON の命名規則が期待と違う | シリアライザの既定設定の不一致 | builder.Services.Configure<HttpJsonOptions> で PropertyNamingPolicy を設定。 |
| 本番で仕様書を晒したくない | アクセス制御の設定漏れ | RequireAuthorization/IP 制限/エッジ側のルールで保護。 |
設計品質を高めるチェックリスト
- エンドポイント命名規則(名詞・複数形/リソース指向)を統一しているか。
- ステータスコードに一貫性があるか(2xx/4xx/5xx の切り分け)。
- ページング・ソート・フィルタのクエリパラメータ仕様が明確か。
- エラー応答(
ProblemDetailsなど)のフォーマットを固定しているか。 - スキーマの再利用(共通 DTO / コンポーネント)で DRY を担保しているか。
- 認証・認可の要求(ヘッダ、スコープ、ロール)を仕様に記述しているか。
- 破壊的変更の方針(バージョニング/廃止通知)がドキュメント化されているか。
移行戦略:既存 Swagger(Swashbuckle)からの段階的乗り換え
- 併用期間を作る:既存 UI は残しつつ、
Microsoft.AspNetCore.OpenApiで仕様も並行出力。 - UI 切替の周知:閲覧者(社内/社外)に新 UI の導線を案内。最初は Redoc の静的ページが容易。
- CI の一本化:OpenAPI の単一出力(JSON/YAML)を真実のソースにし、SDK 生成・テストを統合。
- 最終撤去:既存 UI のメンテコストをゼロ化し、標準のみで運用。
OpenAPI 2.0(Swagger 2.0)しか受け付けないクライアントがいる場合
古いゲートウェイやツールが OpenAPI 3.x に非対応の場合でも、運用で回避できます。
- バージョン変換:CI で 3.x → 2.0 へダウンコンバート(CLI ツール等)。
- 長期的には:クライアント側の更新を促し、3.x へ寄せていくのが本筋です。
社内外での公開パターン(ユースケース別ガイド)
| ユースケース | 仕様書の配置 | UI | アクセス制御 |
|---|---|---|---|
| 社内のみ | アプリ配下の /openapi.json | Stoplight Elements(ポータル化) | SSO/ネットワーク制限 |
| 外部公開(読み物型) | 静的サイト(CDN) | Redoc/Slate | 仕様は公開、モック/実行は制限 |
| クライアント開発者向け | 成果物として JSON/YAML を配布 | Postman コレクション | 配布チャネル管理 |
品質を一段上げるためのコード断片集
要求ヘッダの明示
API キーやトレース ID など、必須ヘッダは仕様に残しましょう(エンドポイントの説明へ記述)。
app.MapGet("/reports", (HttpContext ctx) =>
{
// ここでヘッダの検証などを行う
return Results.Ok(new { Ok = true });
})
.WithSummary("レポート一覧を取得")
.WithDescription("必須ヘッダ: X-Trace-Id")
.WithTags("Reports")
.Produces(StatusCodes.Status200OK)
.WithOpenApi();
ページングの表現
app.MapGet("/items", (int page = 1, int pageSize = 50) =>
{
return Results.Ok(new {
page, pageSize,
total = 1234,
data = new[] { new { id = 1, name = "sample" } }
});
})
.WithSummary("アイテム一覧(ページング)")
.WithDescription("page と pageSize をクエリで指定可能。最大 100。")
.WithTags("Items")
.Produces(StatusCodes.Status200OK)
.WithOpenApi();
セキュアな設計と配慮事項
- ドキュメントの公開範囲:
RequireAuthorizationやネットワーク制限で保護。 - サンプルに実データを入れない:OpenAPI の example は匿名化・ダミーに。
- エラーメッセージの扱い:内部設計情報や機密を出さない。
ProblemDetailsの標準形に寄せる。
よくある質問(FAQ)
Q. 仕様書のパスはどこにすべき? A. /openapi.json や /openapi.yaml の直下が無難です。API のベースパスと並列に置くと、ゲートウェイやクライアントから参照しやすくなります。 Q. YAML と JSON はどちらを配ればよい? A. ツール互換の観点では JSON が無難です。人間が読むなら YAML の可読性が高いので、両方配布がベストです。 Q. 仕様とコードの乖離を防ぐには? A. 「コードに WithOpenApi()、WithSummary、WithDescription、Produces をきちんと付ける」「CI で毎回 openapi.json を出す」の二段構えが効きます。 Q. 将来 UI を変えたくなったら? A. 仕様と UI を分離していれば、差し替えは容易です。仕様の URL(/openapi.json)だけ固定しておき、UI 側から参照する設計にしましょう。
サンプル:初心者向けフル Program.cs(Minimal API)
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
// JSON の命名規則(必要なら)
builder.Services.Configure(opts =>
{
opts.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});
// OpenAPI(仕様自動生成)
builder.Services.AddOpenApi();
var app = builder.Build();
// v1 グループ(将来の拡張に備えてグルーピング)
var v1 = app.MapGroup("/api/v1").WithTags("v1");
// GET
v1.MapGet("/weather", () =>
{
var data = new[] { new WeatherForecast(DateOnly.FromDateTime(DateTime.Now), 21, "Sunny") };
return Results.Ok(data);
})
.WithName("GetWeatherV1")
.WithSummary("天気一覧を取得")
.WithDescription("現在時刻基準のサンプルデータを返します。")
.Produces(StatusCodes.Status200OK)
.WithOpenApi();
// POST
v1.MapPost("/orders", (CreateOrderRequest req) =>
{
if (string.IsNullOrWhiteSpace(req.ProductCode))
return Results.BadRequest(new ProblemDetails { Title = "ProductCode is required" });
var created = new OrderDto(1001, "CREATED", req.ProductCode, req.Quantity);
return Results.Created($"/api/v1/orders/{created.Id}", created);
})
.WithName("CreateOrderV1")
.WithSummary("注文を新規作成")
.WithDescription("商品コードと数量を指定して注文を作成します。")
.Produces(StatusCodes.Status201Created)
.ProducesProblem(StatusCodes.Status400BadRequest)
.WithOpenApi();
// OpenAPI の公開(JSON/YAML)
app.MapOpenApi("/openapi.json");
app.MapOpenApi("/openapi.yaml");
app.Run();
public record WeatherForecast(DateOnly Date, int TemperatureC, string Summary);
public record CreateOrderRequest(string ProductCode, int Quantity);
public record OrderDto(int Id, string Status, string ProductCode, int Quantity);
まとめ:標準だけで「保守しやすく、変化に強い」APIドキュメント運用
- 仕様は Microsoft.AspNetCore.OpenApi で自動生成(OpenAPI 3.0/3.1)。
- UI は要件に合わせて選択(Redoc / Stoplight Elements / Postman / Slate)。
- 本番は保護(認証・ネットワーク制限)。
- CI で常に最新(起動→取得→配布)。
- メタデータの充実(Summary/Description/Tags/Produces で可読性UP)。
これで「Swagger UI がなくても困らない」どころか、むしろ要件ごとに最適化された API ドキュメント体験を提供できます。まずは最小構成で /openapi.json を出すところから始め、必要に応じて UI と運用を足していきましょう。
付録:導入手順(おさらい)
- プロジェクトに
Microsoft.AspNetCore.OpenApiを追加。 builder.Services.AddOpenApi()を呼ぶ。- 各エンドポイントに
WithOpenApi()とメタデータ(Summary/Description/Tags/Produces)。 app.MapOpenApi("/openapi.json")(必要なら YAML も)。- 閲覧用 UI は用途別に選択して別配信。
- 本番では閲覧制御を必ず設定。
- CI で仕様の取得と配布を自動化。
付録:導入時の判断早見表
| 判断ポイント | 推奨 | 理由 |
|---|---|---|
| 仕様の公開形式 | JSON と YAML の併用 | ツール互換性と可読性のバランスが良い |
| UI の選定 | Redoc(読み物)/ Elements(試用)/ Postman(テスト) | 用途ごとの最適解を選ぶため |
| 本番の公開範囲 | 認証必須 + ネットワーク制限 | 内部情報の露出を防ぐ |
| 変更管理 | CI で毎回生成し差分監視 | コードと仕様の乖離を防止 |
以上、ASP.NET Core 標準だけで “Swagger 以外” の API ドキュメント運用を実現するための実践ガイドでした。OpenAPI を中核に、UI と運用を柔らかく組み合わせれば、開発体験と保守性の両取りができます。

コメント