关于 dotnet-api-versioning
为 ASP.NET Core 开发者提供基于 Asp.Versioning 的 API 版本管理指南,涵盖 Minimal APIs 与 MVC。内容包括多种版本读取策略、弃用通知及旧版包迁移。
主要功能
- 01URL、请求头和查询参数版本控制
- 02配置 Minimal APIs 与 MVC
- 03组合多个版本读取器
- 04设置 Sunset 弃用策略
- 05迁移旧版 NuGet 包
适用场景
- 01为新 API 选择版本标识策略
- 02为 API 版本提供弃用和迁移通知
- 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/)是首选方案,简单易用且易于发现。本技能涵盖 URL、标头和查询字符串版本控制,以及 Minimal API 和 MVC 控制器的配置、弃用策略的实施,以及从旧版程序包迁移。
不在范围内: 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.Http | Minimal API | 当前 |
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 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.Versioning | Asp.Versioning |
Microsoft.AspNetCore.Mvc.ApiExplorer | Asp.Versioning.ApiExplorer |
主要迁移步骤:
- 替换 NuGet 程序包引用
- 将
using指令从Microsoft.AspNetCore.Mvc.Versioning更新为Asp.Versioning - 将服务注册从
services.AddApiVersioning()(旧版扩展方法)更新为Asp.Versioning提供的当前扩展方法 - 检查所有自定义
IApiVersionReader实现是否存在破坏性变更
详细步骤请参阅迁移指南。
版本策略决策指南
| 策略 | 优点 | 缺点 | 最适合 |
|---|---|---|---|
URL 路径段(/api/v1/) | 简单、直观、可缓存,兼容性好 | URL 会随版本变化 | 公共 API、大多数项目(首选) |
请求头(X-Api-Version: 1.0) | URL 简洁,无需更改路径 | 不易发现,测试更困难 | 客户端受控的内部 API |
查询字符串(?api-version=1.0) | 易于添加,无需更改路径 | URL 混杂,存在缓存键问题 | 快速原型开发、兼容旧版 |
建议: 所有新项目都从 URL 路径段版本控制开始。仅在从现有策略迁移,或特定客户端限制要求时,才添加请求头或查询字符串读取方式。
Agent 注意事项
- 新项目不要使用旧版
Microsoft.AspNetCore.Mvc.Versioning包——请使用Asp.Versioning.Http(Minimal APIs)或Asp.Versioning.Mvc(MVC 控制器)。 - 不要在包引用中硬编码版本号——使用版本范围(例如
8.*),以确保包版本与最新的兼容版本一致。 - 不要忘记设置
ReportApiVersions = true——否则客户端无法从响应头中发现可用版本。 - 不要以不一致的方式混用
MapToApiVersion和路由组前缀——每个路由组都应只面向一个 API 版本。 - 弃用版本时不要遗漏日落策略——始终提供日落日期和迁移链接,以便使用者做好规划。
- 公共 API 不要使用
AssumeDefaultVersionWhenUnspecified = true——这会掩盖对使用者的版本控制要求。应要求明确选择版本。
前提条件
- .NET 8.0+(LTS 基准)
- 用于 Minimal APIs 的
Asp.Versioning.Http - 用于集成 API Explorer 的 MVC 控制器的
Asp.Versioning.Mvc.ApiExplorer