Loading...
Loading...
Compare original and translation side by side
Asp.Versioning/api/v1/Asp.Versioning/api/v1/| 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 |
<PackageReference Include="Asp.Versioning.Http" Version="8.*" /><PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.*" />| 包名称 | 适用场景 | 状态 |
|---|---|---|
| Minimal APIs | 当前可用 |
| MVC控制器 + API Explorer | 当前可用 |
| MVC控制器(无API Explorer) | 当前可用 |
| MVC控制器 | 旧版 -- 迁移至 |
| MVC + API Explorer | 旧版 -- 迁移至 |
<PackageReference Include="Asp.Versioning.Http" Version="8.*" /><PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.*" />/api/v1/products/api/v1/productsbuilder.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()));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()));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());
}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());
}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.0builder.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.0api-versionbuilder.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.comapi-versionbuilder.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.comoptions.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("X-Api-Version"),
new QueryStringApiVersionReader("api-version"));options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("X-Api-Version"),
new QueryStringApiVersionReader("api-version"));Sunsetbuilder.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");
});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"Sunsetbuilder.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");
});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"// 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
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 { }Microsoft.AspNetCore.Mvc.VersioningAsp.Versioning.MvcAsp.Versioning.Http| Legacy namespace | Current namespace |
|---|---|
| |
| |
usingMicrosoft.AspNetCore.Mvc.VersioningAsp.Versioningservices.AddApiVersioning()Asp.VersioningIApiVersionReaderMicrosoft.AspNetCore.Mvc.VersioningAsp.Versioning.MvcAsp.Versioning.Http| 旧版命名空间 | 当前命名空间 |
|---|---|
| |
| |
usingMicrosoft.AspNetCore.Mvc.VersioningAsp.Versioningservices.AddApiVersioning()Asp.VersioningIApiVersionReader| 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 |
| 策略 | 优点 | 缺点 | 最佳适用场景 |
|---|---|---|---|
URL路径段 ( | 简单、可见、支持缓存、全场景兼容 | 每个版本对应不同URL | 公开API、大多数项目(首选) |
请求头 ( | URL整洁、无需修改路径 | 可发现性低、测试难度大 | 客户端可控的内部API |
查询字符串 ( | 易于添加、无需修改路径 | 污染URL、缓存键冲突 | 快速原型开发、旧版兼容 |
Microsoft.AspNetCore.Mvc.VersioningAsp.Versioning.HttpAsp.Versioning.Mvc8.*ReportApiVersions = trueMapToApiVersionAssumeDefaultVersionWhenUnspecified = trueMicrosoft.AspNetCore.Mvc.VersioningAsp.Versioning.HttpAsp.Versioning.Mvc8.*ReportApiVersions = trueMapToApiVersionAssumeDefaultVersionWhenUnspecified = trueAsp.Versioning.HttpAsp.Versioning.Mvc.ApiExplorerAsp.Versioning.HttpAsp.Versioning.Mvc.ApiExplorer