日程Fit|「いつ空いてますか?」の往復はもう不要。候補日を選んでURLを送るだけ|登録不要|今すぐ無料で使う →

ASP.NET Core 9でSwaggerなしでもOK:Microsoft.AspNetCore.OpenApiでOpenAPI 3.1対応APIドキュメントを作る完全ガイド

「Swagger(Swashbuckle)を入れずに、ASP.NET Core 標準だけで“使える”APIドキュメントを用意したい」。そんなときに役立つのが、.NET の公式パッケージ Microsoft.AspNetCore.OpenApi です。本記事は、OpenAPI 3.0/3.1 の仕様書(JSON/YAML)を自動生成し、好みのUIで閲覧・試用できるまでを、現場投入できる具体性で徹底解説します。

日程Fit。無料・登録不要。「いつ空いてる?」を、ひとつのリンクで。リンクを送って、○△×でかんたん日程調整。無料で日程を作る。
目次

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 の品質はメタデータで決まります。WithSummaryWithDescriptionWithTagsProduces を積極的に使いましょう。

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) =&gt;
{
    return id switch
    {
        &lt;= 0 =&gt; Results.BadRequest(new ProblemDetails { Title = "Invalid id" }),
        42 =&gt; Results.NotFound(),
        _ =&gt; Results.Ok(new OrderDto(id, "PAID"))
    };
})
.WithName("GetOrder")
.WithSummary("注文の取得")
.WithDescription("注文IDを指定して詳細を返します。")
.WithTags("Orders")
.Produces&lt;OrderDto&gt;(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)、あるいは v1v2 のエンドポイントを構造的に分離しておくと、仕様書の読み手が混乱しません。ルーティング構成とタグ付けの一貫性が鍵です。

CD/CI に組み込んで「常に最新」を保証する

最小構成の自動生成手順(例)

  1. ビルド完了後にアプリを一時起動(テストポート)。
  2. /openapi.json を HTTP 経由で取得し、アーティファクトとして保存。
  3. 必要に応じて YAML 変換や整形を実施。
  4. 静的ホスティング(例:社内 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 配信)

  1. 単一 HTML を用意し、openapi.json のパスを差し込む。
  2. CDN またはバンドルしたスクリプトを読み込む(環境に合わせて選択)。
  3. 静的サイトとして配置(Git-based、オブジェクトストレージ等)。
&lt;!-- redoc.html の例(CDN の URL は運用に合わせて設定) --&gt;
&lt;!doctype html&gt;
&lt;html&gt;
  &lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;title&gt;API Docs&lt;/title&gt;&lt;/head&gt;
  &lt;body&gt;
    &lt;redoc spec-url="/openapi.json"&gt;&lt;/redoc&gt;
    &lt;script src="&lt;CDNのスクリプトURL&gt;"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;

Stoplight Elements(試用&モック志向)

&lt;!-- elements.html --&gt;
&lt;!doctype html&gt;
&lt;html&gt;
  &lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;title&gt;API Docs&lt;/title&gt;&lt;/head&gt;
  &lt;body&gt;
    &lt;elements-api
      apiDescriptionUrl="/openapi.json"
      router="hash"&gt;
    &lt;/elements-api&gt;
    &lt;script src="&lt;Elements のスクリプトURL&gt;"&gt;&lt;/script&gt;
    &lt;link rel="stylesheet" href="&lt;Elements のCSS URL&gt;" /&gt;
  &lt;/body&gt;
&lt;/html&gt;

Postman(テストと共有を一体化)

  • ワークスペースに openapi.json をインポート。
  • コレクションとして保存し、環境変数にベース URL / トークンを設定。
  • テストスクリプトを用意して回帰テストに流用。

トラブルシューティング

症状原因の典型対処
/openapi.json が 404MapOpenApi を呼んでいない/パス相違公開パスを確認。環境によってパスベースのリバースプロキシにも注意。
エンドポイントが仕様に出てこないWithOpenApi() を付与していない/非公開のルートグループに含めたエンドポイント側に WithOpenApi() を付与。グループの構造も見直し。
レスポンス型が曖昧IResult を無限定で返しているResults<...> で戻り型を限定し、Produces で明示。
JSON の命名規則が期待と違うシリアライザの既定設定の不一致builder.Services.Configure<HttpJsonOptions>PropertyNamingPolicy を設定。
本番で仕様書を晒したくないアクセス制御の設定漏れRequireAuthorization/IP 制限/エッジ側のルールで保護。

設計品質を高めるチェックリスト

  • エンドポイント命名規則(名詞・複数形/リソース指向)を統一しているか。
  • ステータスコードに一貫性があるか(2xx/4xx/5xx の切り分け)。
  • ページング・ソート・フィルタのクエリパラメータ仕様が明確か。
  • エラー応答ProblemDetails など)のフォーマットを固定しているか。
  • スキーマの再利用(共通 DTO / コンポーネント)で DRY を担保しているか。
  • 認証・認可の要求(ヘッダ、スコープ、ロール)を仕様に記述しているか。
  • 破壊的変更の方針(バージョニング/廃止通知)がドキュメント化されているか。

移行戦略:既存 Swagger(Swashbuckle)からの段階的乗り換え

  1. 併用期間を作る:既存 UI は残しつつ、Microsoft.AspNetCore.OpenApi で仕様も並行出力。
  2. UI 切替の周知:閲覧者(社内/社外)に新 UI の導線を案内。最初は Redoc の静的ページが容易。
  3. CI の一本化:OpenAPI の単一出力(JSON/YAML)を真実のソースにし、SDK 生成・テストを統合。
  4. 最終撤去:既存 UI のメンテコストをゼロ化し、標準のみで運用。

OpenAPI 2.0(Swagger 2.0)しか受け付けないクライアントがいる場合

古いゲートウェイやツールが OpenAPI 3.x に非対応の場合でも、運用で回避できます。

  • バージョン変換:CI で 3.x → 2.0 へダウンコンバート(CLI ツール等)。
  • 長期的には:クライアント側の更新を促し、3.x へ寄せていくのが本筋です。

社内外での公開パターン(ユースケース別ガイド)

ユースケース仕様書の配置UIアクセス制御
社内のみアプリ配下の /openapi.jsonStoplight Elements(ポータル化)SSO/ネットワーク制限
外部公開(読み物型)静的サイト(CDN)Redoc/Slate仕様は公開、モック/実行は制限
クライアント開発者向け成果物として JSON/YAML を配布Postman コレクション配布チャネル管理

品質を一段上げるためのコード断片集

要求ヘッダの明示

API キーやトレース ID など、必須ヘッダは仕様に残しましょう(エンドポイントの説明へ記述)。

app.MapGet("/reports", (HttpContext ctx) =&gt;
{
    // ここでヘッダの検証などを行う
    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) =&gt;
{
    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()WithSummaryWithDescriptionProduces をきちんと付ける」「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 と運用を足していきましょう。


付録:導入手順(おさらい)

  1. プロジェクトに Microsoft.AspNetCore.OpenApi を追加。
  2. builder.Services.AddOpenApi() を呼ぶ。
  3. 各エンドポイントに WithOpenApi() とメタデータ(Summary/Description/Tags/Produces)。
  4. app.MapOpenApi("/openapi.json")(必要なら YAML も)。
  5. 閲覧用 UI は用途別に選択して別配信。
  6. 本番では閲覧制御を必ず設定。
  7. CI で仕様の取得と配布を自動化。

付録:導入時の判断早見表

判断ポイント推奨理由
仕様の公開形式JSON と YAML の併用ツール互換性と可読性のバランスが良い
UI の選定Redoc(読み物)/ Elements(試用)/ Postman(テスト)用途ごとの最適解を選ぶため
本番の公開範囲認証必須 + ネットワーク制限内部情報の露出を防ぐ
変更管理CI で毎回生成し差分監視コードと仕様の乖離を防止

以上、ASP.NET Core 標準だけで “Swagger 以外” の API ドキュメント運用を実現するための実践ガイドでした。OpenAPI を中核に、UI と運用を柔らかく組み合わせれば、開発体験と保守性の両取りができます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次