dotnet-api-versioning

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

dotnet-api-versioning

.NET API版本控制

API versioning strategies for ASP.NET Core using the
Asp.Versioning
library family. URL segment versioning (
/api/v1/
) is the preferred approach for simplicity and discoverability. This skill covers URL, header, and query string versioning with configuration for both Minimal APIs and MVC controllers, sunset policy enforcement, and migration from legacy packages.
使用
Asp.Versioning
库家族实现ASP.NET Core的API版本控制策略。URL路径段版本控制(
/api/v1/
)因简洁性和可发现性成为首选方案。本技能涵盖URL、请求头和查询字符串三种版本控制方式,包含Minimal APIs和MVC控制器的配置、日落策略实施,以及从旧版包的迁移指南。

Scope

适用范围

  • URL segment, header, and query string versioning strategies
  • Asp.Versioning configuration for Minimal APIs and MVC controllers
  • Sunset policies and version deprecation (RFC 8594)
  • Combining version readers for migration scenarios
  • Legacy package migration guidance
  • URL路径段、请求头和查询字符串版本控制策略
  • 针对Minimal APIs和MVC控制器的Asp.Versioning配置
  • 日落策略与版本弃用(RFC 8594)
  • 迁移场景下的版本读取器组合
  • 旧版包迁移指南

Out of scope

不适用范围

  • Minimal API endpoint patterns (route groups, filters, TypedResults) -- see [skill:dotnet-minimal-apis]
  • OpenAPI document generation per API version -- see [skill:dotnet-openapi]
  • Authentication and authorization per version -- see [skill:dotnet-api-security]
Cross-references: [skill:dotnet-minimal-apis] for Minimal API endpoint patterns, [skill:dotnet-openapi] for versioned OpenAPI documents.

  • Minimal API端点模式(路由组、过滤器、TypedResults)——请参考[skill:dotnet-minimal-apis]
  • 按API版本生成OpenAPI文档——请参考[skill:dotnet-openapi]
  • 按版本进行身份验证与授权——请参考[skill:dotnet-api-security]
交叉引用:Minimal API端点模式请参考[skill:dotnet-minimal-apis],带版本的OpenAPI文档请参考[skill:dotnet-openapi]。

Package Landscape

包生态

PackageTargetStatus
Asp.Versioning.Http
Minimal APIsCurrent
Asp.Versioning.Mvc.ApiExplorer
MVC controllers + API ExplorerCurrent
Asp.Versioning.Mvc
MVC controllers (no API Explorer)Current
Microsoft.AspNetCore.Mvc.Versioning
MVC controllersLegacy -- migrate to
Asp.Versioning.Mvc
Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
MVC + API ExplorerLegacy -- migrate to
Asp.Versioning.Mvc.ApiExplorer
Install for Minimal APIs:
xml
<PackageReference Include="Asp.Versioning.Http" Version="8.*" />
Install for MVC controllers:
xml
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.*" />

包名称适用场景状态
Asp.Versioning.Http
Minimal APIs当前可用
Asp.Versioning.Mvc.ApiExplorer
MVC控制器 + API Explorer当前可用
Asp.Versioning.Mvc
MVC控制器(无API Explorer)当前可用
Microsoft.AspNetCore.Mvc.Versioning
MVC控制器旧版 -- 迁移至
Asp.Versioning.Mvc
Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
MVC + API Explorer旧版 -- 迁移至
Asp.Versioning.Mvc.ApiExplorer
Minimal APIs安装命令:
xml
<PackageReference Include="Asp.Versioning.Http" Version="8.*" />
MVC控制器安装命令:
xml
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.*" />

URL Segment Versioning (Preferred)

URL路径段版本控制(首选)

URL segment versioning embeds the version in the path (
/api/v1/products
). It is the simplest strategy, works with all HTTP clients, is cacheable, and clearly visible in logs and documentation.
URL路径段版本控制将版本嵌入路径中(
/api/v1/products
)。这是最简单的策略,适用于所有HTTP客户端,支持缓存,且在日志和文档中清晰可见。

Minimal APIs

Minimal APIs配置

csharp
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()));
csharp
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true; // 添加api-supported-versions响应头
    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: 返回基础产品信息
v1.MapGet("/", async (AppDbContext db) =>
    TypedResults.Ok(await db.Products
        .Select(p => new ProductV1Dto(p.Id, p.Name, p.Price))
        .ToListAsync()));

// V2: 返回包含分类的扩展产品信息
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 Controllers

MVC控制器配置

csharp
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());
}

csharp
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"; // 示例:v1, v2
    options.SubstituteApiVersionInUrl = true;
});

// V1控制器
[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]令牌
[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());
}

Header Versioning

请求头版本控制

Header versioning reads the API version from a custom request header. Keeps URLs clean but is less discoverable and harder to test from a browser.
csharp
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
    options.ApiVersionReader = new HeaderApiVersionReader("X-Api-Version");
});
Client request:
http
GET /api/products HTTP/1.1
Host: api.example.com
X-Api-Version: 2.0

请求头版本控制从自定义请求头中读取API版本。保持URL整洁,但可发现性较低,且难以从浏览器直接测试。
csharp
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
    options.ApiVersionReader = new HeaderApiVersionReader("X-Api-Version");
});
客户端请求示例:
http
GET /api/products HTTP/1.1
Host: api.example.com
X-Api-Version: 2.0

Query String Versioning

查询字符串版本控制

Query string versioning uses a query parameter (default:
api-version
). Simple to use but pollutes URLs and may conflict with caching strategies.
csharp
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
    options.ApiVersionReader = new QueryStringApiVersionReader("api-version");
});
Client request:
http
GET /api/products?api-version=2.0 HTTP/1.1
Host: api.example.com

查询字符串版本控制使用查询参数(默认:
api-version
)。使用简单,但会污染URL,且可能与缓存策略冲突。
csharp
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
    options.ApiVersionReader = new QueryStringApiVersionReader("api-version");
});
客户端请求示例:
http
GET /api/products?api-version=2.0 HTTP/1.1
Host: api.example.com

Combining Version Readers

组合版本读取器

Multiple readers can be combined. The first reader that resolves a version wins. This is useful during migration from one strategy to another:
csharp
options.ApiVersionReader = ApiVersionReader.Combine(
    new UrlSegmentApiVersionReader(),
    new HeaderApiVersionReader("X-Api-Version"),
    new QueryStringApiVersionReader("api-version"));

可以组合多个版本读取器,第一个解析到版本的读取器生效。这在从一种策略迁移到另一种策略时非常有用:
csharp
options.ApiVersionReader = ApiVersionReader.Combine(
    new UrlSegmentApiVersionReader(),
    new HeaderApiVersionReader("X-Api-Version"),
    new QueryStringApiVersionReader("api-version"));

Sunset Policies

日落策略

Sunset policies communicate to consumers that an API version is deprecated and will be removed. The
Sunset
HTTP response header follows RFC 8594.
csharp
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");
});
Response headers for a v1 request:
http
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"
日落策略用于告知消费者某个API版本已被弃用并将被移除。
Sunset
HTTP响应头遵循RFC 8594标准。
csharp
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到V2迁移指南")
            .Type("text/html");
});
V1版本请求的响应头:
http
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到V2迁移指南"; type="text/html"

Deprecating a Version

标记版本为弃用

Mark a version as deprecated using the version set (Minimal APIs) or attribute (MVC):
csharp
// 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 { }

通过版本集(Minimal APIs)或特性(MVC)标记版本为弃用:
csharp
// Minimal APIs
var versionSet = app.NewApiVersionSet()
    .HasApiVersion(new ApiVersion(1, 0))
    .HasDeprecatedApiVersion(new ApiVersion(1, 0))
    .HasApiVersion(new ApiVersion(2, 0))
    .ReportApiVersions()
    .Build();

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

Migration from Legacy Packages

从旧版包迁移

Projects using
Microsoft.AspNetCore.Mvc.Versioning
should migrate to
Asp.Versioning.Mvc
(or
Asp.Versioning.Http
for Minimal APIs). The API surface is largely compatible with namespace changes:
Legacy namespaceCurrent namespace
Microsoft.AspNetCore.Mvc.Versioning
Asp.Versioning
Microsoft.AspNetCore.Mvc.ApiExplorer
Asp.Versioning.ApiExplorer
Key migration steps:
  1. Replace NuGet package references
  2. Update
    using
    directives from
    Microsoft.AspNetCore.Mvc.Versioning
    to
    Asp.Versioning
  3. Update service registration from
    services.AddApiVersioning()
    (legacy extension) to the current extension from
    Asp.Versioning
  4. Review any custom
    IApiVersionReader
    implementations for breaking changes
See the migration guide for detailed steps.

使用
Microsoft.AspNetCore.Mvc.Versioning
的项目应迁移至
Asp.Versioning.Mvc
(Minimal APIs则使用
Asp.Versioning.Http
)。API接口基本兼容,仅需修改命名空间:
旧版命名空间当前命名空间
Microsoft.AspNetCore.Mvc.Versioning
Asp.Versioning
Microsoft.AspNetCore.Mvc.ApiExplorer
Asp.Versioning.ApiExplorer
关键迁移步骤:
  1. 替换NuGet包引用
  2. using
    指令从
    Microsoft.AspNetCore.Mvc.Versioning
    更新为
    Asp.Versioning
  3. 将服务注册从旧版的
    services.AddApiVersioning()
    更新为
    Asp.Versioning
    提供的当前扩展方法
  4. 检查自定义
    IApiVersionReader
    实现是否存在破坏性变更
详细步骤请参考迁移指南

Version Strategy Decision Guide

版本策略决策指南

StrategyProsConsBest for
URL segment (
/api/v1/
)
Simple, visible, cacheable, works everywhereURL changes per versionPublic APIs, most projects (preferred)
Header (
X-Api-Version: 1.0
)
Clean URLs, no path changesLess discoverable, harder to testInternal APIs with controlled clients
Query string (
?api-version=1.0
)
Easy to add, no path changesPollutes URL, cache key issuesQuick prototyping, legacy compatibility
Recommendation: Start with URL segment versioning for all new projects. Add header or query string readers only when migrating from an existing strategy or when specific client constraints require it.

策略优点缺点最佳适用场景
URL路径段 (
/api/v1/
)
简单、可见、支持缓存、全场景兼容每个版本对应不同URL公开API、大多数项目(首选)
请求头 (
X-Api-Version: 1.0
)
URL整洁、无需修改路径可发现性低、测试难度大客户端可控的内部API
查询字符串 (
?api-version=1.0
)
易于添加、无需修改路径污染URL、缓存键冲突快速原型开发、旧版兼容
推荐建议: 所有新项目均从URL路径段版本控制开始。仅在从现有策略迁移或特定客户端约束要求时,才添加请求头或查询字符串读取器。

Agent Gotchas

注意事项

  1. Do not use the legacy
    Microsoft.AspNetCore.Mvc.Versioning
    package for new projects
    -- use
    Asp.Versioning.Http
    (Minimal APIs) or
    Asp.Versioning.Mvc
    (MVC controllers).
  2. Do not hardcode version numbers in package references -- use version ranges (e.g.,
    8.*
    ) so the package version matches the latest compatible release.
  3. Do not forget
    ReportApiVersions = true
    -- without it, clients cannot discover available versions from response headers.
  4. Do not mix
    MapToApiVersion
    and route group prefixes inconsistently
    -- each route group should target exactly one API version.
  5. Do not deprecate a version without a sunset policy -- always provide a sunset date and migration link so consumers can plan.
  6. Do not use
    AssumeDefaultVersionWhenUnspecified = true
    for public APIs
    -- it hides versioning requirements from consumers. Require explicit version selection instead.

  1. 新项目请勿使用旧版
    Microsoft.AspNetCore.Mvc.Versioning
    ——请使用
    Asp.Versioning.Http
    (Minimal APIs)或
    Asp.Versioning.Mvc
    (MVC控制器)。
  2. 请勿在包引用中硬编码版本号——使用版本范围(如
    8.*
    ),使包版本与最新兼容版本保持一致。
  3. 请勿忘记设置
    ReportApiVersions = true
    ——否则客户端无法从响应头中发现可用版本。
  4. 请勿不一致地混合使用
    MapToApiVersion
    和路由组前缀
    ——每个路由组应仅对应一个API版本。
  5. 请勿在未设置日落策略的情况下弃用版本——始终提供日落日期和迁移链接,以便消费者规划迁移。
  6. 公开API请勿设置
    AssumeDefaultVersionWhenUnspecified = true
    ——这会向消费者隐藏版本控制要求,应要求显式指定版本。

Prerequisites

前置要求

  • .NET 8.0+ (LTS baseline)
  • Asp.Versioning.Http
    for Minimal APIs
  • Asp.Versioning.Mvc.ApiExplorer
    for MVC controllers with API Explorer integration

  • .NET 8.0+(LTS基线版本)
  • Minimal APIs需使用
    Asp.Versioning.Http
  • 带API Explorer集成的MVC控制器需使用
    Asp.Versioning.Mvc.ApiExplorer

References

参考资料