dotnet-api-versioning
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesedotnet-api-versioning
.NET API版本控制
API versioning strategies for ASP.NET Core using the library family. URL segment versioning () 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/api/v1/使用库家族实现ASP.NET Core的API版本控制策略。URL路径段版本控制()因简洁性和可发现性成为首选方案。本技能涵盖URL、请求头和查询字符串三种版本控制方式,包含Minimal APIs和MVC控制器的配置、日落策略实施,以及从旧版包的迁移指南。
Asp.Versioning/api/v1/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
包生态
| Package | Target | Status |
|---|---|---|
| Minimal APIs | Current |
| MVC controllers + API Explorer | Current |
| MVC controllers (no API Explorer) | Current |
| MVC controllers | Legacy -- migrate to |
| MVC + API Explorer | Legacy -- migrate to |
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.*" />| 包名称 | 适用场景 | 状态 |
|---|---|---|
| Minimal APIs | 当前可用 |
| MVC控制器 + API Explorer | 当前可用 |
| MVC控制器(无API Explorer) | 当前可用 |
| MVC控制器 | 旧版 -- 迁移至 |
| MVC + API Explorer | 旧版 -- 迁移至 |
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 (). It is the simplest strategy, works with all HTTP clients, is cacheable, and clearly visible in logs and documentation.
/api/v1/productsURL路径段版本控制将版本嵌入路径中()。这是最简单的策略,适用于所有HTTP客户端,支持缓存,且在日志和文档中清晰可见。
/api/v1/productsMinimal 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.0Query String Versioning
查询字符串版本控制
Query string versioning uses a query parameter (default: ). Simple to use but pollutes URLs and may conflict with caching strategies.
api-versioncsharp
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查询字符串版本控制使用查询参数(默认:)。使用简单,但会污染URL,且可能与缓存策略冲突。
api-versioncsharp
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.comCombining 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 HTTP response header follows RFC 8594.
Sunsetcsharp
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版本已被弃用并将被移除。 HTTP响应头遵循RFC 8594标准。
Sunsetcsharp
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 should migrate to (or for Minimal APIs). The API surface is largely compatible with namespace changes:
Microsoft.AspNetCore.Mvc.VersioningAsp.Versioning.MvcAsp.Versioning.Http| Legacy namespace | Current namespace |
|---|---|
| |
| |
Key migration steps:
- Replace NuGet package references
- Update directives from
usingtoMicrosoft.AspNetCore.Mvc.VersioningAsp.Versioning - Update service registration from (legacy extension) to the current extension from
services.AddApiVersioning()Asp.Versioning - Review any custom implementations for breaking changes
IApiVersionReader
See the migration guide for detailed steps.
使用的项目应迁移至(Minimal APIs则使用)。API接口基本兼容,仅需修改命名空间:
Microsoft.AspNetCore.Mvc.VersioningAsp.Versioning.MvcAsp.Versioning.Http| 旧版命名空间 | 当前命名空间 |
|---|---|
| |
| |
关键迁移步骤:
- 替换NuGet包引用
- 将指令从
using更新为Microsoft.AspNetCore.Mvc.VersioningAsp.Versioning - 将服务注册从旧版的更新为
services.AddApiVersioning()提供的当前扩展方法Asp.Versioning - 检查自定义实现是否存在破坏性变更
IApiVersionReader
详细步骤请参考迁移指南。
Version Strategy Decision Guide
版本策略决策指南
| Strategy | Pros | Cons | Best for |
|---|---|---|---|
URL segment ( | Simple, visible, cacheable, works everywhere | URL changes per version | Public APIs, most projects (preferred) |
Header ( | Clean URLs, no path changes | Less discoverable, harder to test | Internal APIs with controlled clients |
Query string ( | Easy to add, no path changes | Pollutes URL, cache key issues | Quick 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路径段 ( | 简单、可见、支持缓存、全场景兼容 | 每个版本对应不同URL | 公开API、大多数项目(首选) |
请求头 ( | URL整洁、无需修改路径 | 可发现性低、测试难度大 | 客户端可控的内部API |
查询字符串 ( | 易于添加、无需修改路径 | 污染URL、缓存键冲突 | 快速原型开发、旧版兼容 |
推荐建议: 所有新项目均从URL路径段版本控制开始。仅在从现有策略迁移或特定客户端约束要求时,才添加请求头或查询字符串读取器。
Agent Gotchas
注意事项
- Do not use the legacy package for new projects -- use
Microsoft.AspNetCore.Mvc.Versioning(Minimal APIs) orAsp.Versioning.Http(MVC controllers).Asp.Versioning.Mvc - Do not hardcode version numbers in package references -- use version ranges (e.g., ) so the package version matches the latest compatible release.
8.* - Do not forget -- without it, clients cannot discover available versions from response headers.
ReportApiVersions = true - Do not mix and route group prefixes inconsistently -- each route group should target exactly one API version.
MapToApiVersion - Do not deprecate a version without a sunset policy -- always provide a sunset date and migration link so consumers can plan.
- Do not use for public APIs -- it hides versioning requirements from consumers. Require explicit version selection instead.
AssumeDefaultVersionWhenUnspecified = true
- 新项目请勿使用旧版包——请使用
Microsoft.AspNetCore.Mvc.Versioning(Minimal APIs)或Asp.Versioning.Http(MVC控制器)。Asp.Versioning.Mvc - 请勿在包引用中硬编码版本号——使用版本范围(如),使包版本与最新兼容版本保持一致。
8.* - 请勿忘记设置——否则客户端无法从响应头中发现可用版本。
ReportApiVersions = true - 请勿不一致地混合使用和路由组前缀——每个路由组应仅对应一个API版本。
MapToApiVersion - 请勿在未设置日落策略的情况下弃用版本——始终提供日落日期和迁移链接,以便消费者规划迁移。
- 公开API请勿设置——这会向消费者隐藏版本控制要求,应要求显式指定版本。
AssumeDefaultVersionWhenUnspecified = true
Prerequisites
前置要求
- .NET 8.0+ (LTS baseline)
- for Minimal APIs
Asp.Versioning.Http - for MVC controllers with API Explorer integration
Asp.Versioning.Mvc.ApiExplorer
- .NET 8.0+(LTS基线版本)
- Minimal APIs需使用
Asp.Versioning.Http - 带API Explorer集成的MVC控制器需使用
Asp.Versioning.Mvc.ApiExplorer