Loading...
Loading...
Versions HTTP APIs. Asp.Versioning.Http/Mvc, URL segment, header, query string, sunset.
npx skill4agent add novotnyllc/dotnet-artisan dotnet-api-versioningAsp.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.*" />/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;
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 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.comoptions.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"// 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.VersioningAsp.Versioning.MvcAsp.Versioning.Http| Legacy namespace | Current namespace |
|---|---|
| |
| |
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 |
Microsoft.AspNetCore.Mvc.VersioningAsp.Versioning.HttpAsp.Versioning.Mvc8.*ReportApiVersions = trueMapToApiVersionAssumeDefaultVersionWhenUnspecified = trueAsp.Versioning.HttpAsp.Versioning.Mvc.ApiExplorer