mcpField
D
dotnet-api-versioning

dotnet-api-versioning

github.com/wshaddix開発8668 回インストール35 週間前に更新
リポジトリ

dotnet-api-versioning について

Asp.Versioning を使って Minimal APIs と MVC の API バージョンを管理する、ASP.NET Core 開発者向けガイドです。複数の読み取り方式、廃止通知、旧パッケージからの移行を扱います。

主な機能

  1. 01URL・ヘッダー・クエリでのバージョン管理
  2. 02Minimal APIs と MVC の設定
  3. 03複数のバージョン読み取り方式の併用
  4. 04Sunset ポリシーの設定
  5. 05旧 NuGet パッケージからの移行

活用シーン

  1. 01新しい API のバージョン方式の選定
  2. 02廃止バージョンと移行の利用者への通知
  3. 03旧バージョン管理パッケージから Asp.Versioning への移行

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-api-versioning

インストール後、Claude Code で/dotnet-api-versioningを入力して読み込まれたか確認します。

詳細

dotnet-api-versioning

Asp.Versioning ライブラリファミリーを使用した ASP.NET Core の API バージョニング戦略です。シンプルで見つけやすいため、URL セグメントによるバージョニング(/api/v1/)を推奨します。このスキルでは、Minimal API と MVC コントローラーの両方で使える URL、ヘッダー、クエリ文字列によるバージョニング、サンセットポリシーの適用、レガシーパッケージからの移行について説明します。

対象外: Minimal API のエンドポイントパターン(ルートグループ、フィルター、TypedResults)については、[skill:dotnet-minimal-apis] を参照してください。API バージョンごとの OpenAPI ドキュメント生成については、[skill:dotnet-openapi] を参照してください。バージョンごとの認証と認可については、[skill:dotnet-api-security] を参照してください。

相互参照:[skill:dotnet-minimal-apis] は Minimal API のエンドポイントパターンについて、[skill:dotnet-openapi] はバージョン管理された OpenAPI ドキュメントについて説明しています。


パッケージの概要

パッケージ対象ステータス
Asp.Versioning.HttpMinimal API現行
Asp.Versioning.Mvc.ApiExplorerMVC コントローラー + API Explorer現行
Asp.Versioning.MvcMVC コントローラー(API Explorer なし)現行
Microsoft.AspNetCore.Mvc.VersioningMVC コントローラーレガシー -- Asp.Versioning.Mvc に移行してください
Microsoft.AspNetCore.Mvc.Versioning.ApiExplorerMVC + API Explorerレガシー -- Asp.Versioning.Mvc.ApiExplorer に移行してください

Minimal API 用のインストール:

<PackageReference Include="Asp.Versioning.Http" Version="8.*" />

MVC コントローラー用のインストール:

<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.*" />

URL セグメントによるバージョニング(推奨)

URL セグメントによるバージョニングでは、バージョンをパス(/api/v1/products)に含めます。最もシンプルな戦略で、すべての HTTP クライアントで利用でき、キャッシュ可能で、ログやドキュメントからもバージョンがはっきりとわかります。

Minimal API
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true; // Adds api-supported-versions header
    options.ApiVersionReader = new UrlSegmentApiVersionReader();
});

var app = builder.Build();

var versionSet = app.NewApiVersionSet()
    .HasApiVersion(new ApiVersion(1, 0))
    .HasApiVersion(new ApiVersion(2, 0))
    .ReportApiVersions()
    .Build();

var v1 = app.MapGroup("/api/v{version:apiVersion}/products")
    .WithApiVersionSet(versionSet)
    .MapToApiVersion(new ApiVersion(1, 0));

var v2 = app.MapGroup("/api/v{version:apiVersion}/products")
    .WithApiVersionSet(versionSet)
    .MapToApiVersion(new ApiVersion(2, 0));

// V1: returns basic product info
v1.MapGet("/", async (AppDbContext db) =>
    TypedResults.Ok(await db.Products
        .Select(p => new ProductV1Dto(p.Id, p.Name, p.Price))
        .ToListAsync()));

// V2: returns extended product info with category
v2.MapGet("/", async (AppDbContext db) =>
    TypedResults.Ok(await db.Products
        .Select(p => new ProductV2Dto(p.Id, p.Name, p.Price, p.Category, p.CreatedAt))
        .ToListAsync()));
MVC コントローラー
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
    options.ApiVersionReader = new UrlSegmentApiVersionReader();
})
.AddMvc()
.AddApiExplorer(options =>
{
    options.GroupNameFormat = "'v'VVV"; // e.g., v1, v2
    options.SubstituteApiVersionInUrl = true;
});

// V1 controller
[ApiController]
[Route("api/v{version:apiVersion}/products")]
[ApiVersion("1.0")]
public sealed class ProductsController(AppDbContext db) : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> GetAll() =>
        Ok(await db.Products
            .Select(p => new ProductV1Dto(p.Id, p.Name, p.Price))
            .ToListAsync());
}

// V2 controller -- use explicit route, not [controller] token
[ApiController]
[Route("api/v{version:apiVersion}/products")]
[ApiVersion("2.0")]
public sealed class ProductsV2Controller(AppDbContext db) : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> GetAll() =>
        Ok(await db.Products
            .Select(p => new ProductV2Dto(p.Id, p.Name, p.Price, p.Category, p.CreatedAt))
            .ToListAsync());
}

ヘッダーによるバージョニング

ヘッダーによるバージョニングでは、カスタムリクエストヘッダーから API バージョンを読み取ります。URL を簡潔に保てますが、見つけにくく、ブラウザーからテストするのも困難です。

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
    options.ApiVersionReader = new HeaderApiVersionReader("X-Api-Version");
});

クライアントリクエスト:

GET /api/products HTTP/1.1
Host: api.example.com
X-Api-Version: 2.0

クエリ文字列によるバージョニング

クエリ文字列によるバージョニングでは、クエリパラメーター(既定値:api-version)を使用します。使いやすい一方で、URL が煩雑になり、キャッシュ戦略と競合する場合があります。

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
    options.ApiVersionReader = new QueryStringApiVersionReader("api-version");
});

クライアントリクエスト:

GET /api/products?api-version=2.0 HTTP/1.1
Host: api.example.com

バージョンリーダーの組み合わせ

複数のリーダーを組み合わせることができます。バージョンを解決した最初のリーダーが採用されます。これは、ある戦略から別の戦略へ移行する際に便利です。

options.ApiVersionReader = ApiVersionReader.Combine(
    new UrlSegmentApiVersionReader(),
    new HeaderApiVersionReader("X-Api-Version"),
    new QueryStringApiVersionReader("api-version"));

サンセットポリシー

サンセットポリシーは、API バージョンが非推奨であり、削除される予定であることを利用者に伝えます。Sunset HTTP レスポンスヘッダーは、RFC 8594 に準拠しています。

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(2, 0);
    options.ReportApiVersions = true;
    options.Policies.Sunset(1.0)
        .Effective(new DateTimeOffset(2026, 6, 1, 0, 0, 0, TimeSpan.Zero))
        .Link("https://docs.example.com/api/migration-v1-to-v2")
            .Title("V1 to V2 Migration Guide")
            .Type("text/html");
});

v1 リクエストに対するレスポンスヘッダー:

api-supported-versions: 1.0, 2.0
api-deprecated-versions: 1.0
Sunset: Sun, 01 Jun 2026 00:00:00 GMT
Link: <https://docs.example.com/api/migration-v1-to-v2>; rel="sunset"; title="V1 to V2 Migration Guide"; type="text/html"
バージョンの非推奨化

バージョンセット(Minimal API)または属性(MVC)を使って、バージョンを非推奨としてマークします。

// Minimal APIs
var versionSet = app.NewApiVersionSet()
    .HasApiVersion(new ApiVersion(1, 0))
    .HasDeprecatedApiVersion(new ApiVersion(1, 0))
    .HasApiVersion(new ApiVersion(2, 0))
    .ReportApiVersions()
    .Build();

// MVC controllers
[ApiVersion("1.0", Deprecated = true)]
[ApiVersion("2.0")]
public sealed class ProductsController : ControllerBase { }

レガシーパッケージからの移行

Microsoft.AspNetCore.Mvc.Versioning を使用しているプロジェクトは、Asp.Versioning.Mvc(Minimal API の場合は Asp.Versioning.Http)に移行してください。名前空間を変更すれば、API の大部分はそのまま使用できます。

レガシー名前空間現行の名前空間
Microsoft.AspNetCore.Mvc.VersioningAsp.Versioning
Microsoft.AspNetCore.Mvc.ApiExplorerAsp.Versioning.ApiExplorer

主な移行手順:

  1. NuGet パッケージ参照を置き換えます
  2. using ディレクティブを Microsoft.AspNetCore.Mvc.Versioning から Asp.Versioning に更新します
  3. サービス登録を services.AddApiVersioning()(レガシー拡張メソッド)から、Asp.Versioning の現行拡張メソッドに更新します
  4. カスタムの IApiVersionReader 実装に破壊的変更がないか確認します

詳しい手順については、移行ガイドを参照してください。


バージョン戦略の選択ガイド

戦略メリットデメリット最適な用途
URL セグメント (/api/v1/)シンプルで視認性が高く、キャッシュ可能で、あらゆる環境で動作しますバージョンごとに URL が変わります公開 API、ほとんどのプロジェクト(推奨)
ヘッダー (X-Api-Version: 1.0)URL がすっきりし、パスを変更する必要がありません発見しにくく、テストが難しくなりますクライアントを管理できる内部 API
クエリ文字列 (?api-version=1.0)追加が簡単で、パスを変更する必要がありませんURL が煩雑になり、キャッシュキーの問題が発生します手早いプロトタイピング、レガシーとの互換性

推奨事項: 新規プロジェクトでは、まず URL セグメントによるバージョン管理を採用してください。既存の戦略から移行する場合や、特定のクライアントの制約がある場合にのみ、ヘッダーまたはクエリ文字列の読み取り機能を追加してください。


エージェントでの注意点

  1. 新規プロジェクトではレガシーの Microsoft.AspNetCore.Mvc.Versioning パッケージを使用しないでください -- Asp.Versioning.Http(Minimal API)または Asp.Versioning.Mvc(MVC コントローラー)を使用してください。
  2. パッケージ参照でバージョン番号をハードコードしないでください -- バージョン範囲(例:8.*)を使用し、パッケージのバージョンが最新の互換性のあるリリースと一致するようにしてください。
  3. ReportApiVersions = true を忘れないでください -- これを指定しないと、クライアントはレスポンスヘッダーから利用可能なバージョンを確認できません。
  4. MapToApiVersion とルートグループのプレフィックスを一貫性なく混在させないでください -- 各ルートグループの対象にする API バージョンは必ず 1 つにしてください。
  5. サンセットポリシーなしでバージョンを非推奨にしないでください -- 利用者が計画を立てられるよう、必ずサンセット日と移行リンクを提示してください。
  6. 公開 API では AssumeDefaultVersionWhenUnspecified = true を使用しないでください -- 利用者に必要なバージョン指定が見えなくなります。代わりに、明示的なバージョン選択を必須にしてください。

前提条件

  • .NET 8.0+(LTS の基準バージョン)
  • Minimal API 用の Asp.Versioning.Http
  • API Explorer との統合を行う MVC コントローラー用の Asp.Versioning.Mvc.ApiExplorer

参考資料

コメント