Help us improve
Share bugs, ideas, or general feedback.
From dotnet-claude-kit
Implements API versioning strategies in ASP.NET Core using Asp.Versioning library, supporting URL segments, headers, query strings, deprecation, and OpenAPI integration.
npx claudepluginhub trossitec/dotnet-claude-kit --plugin dotnet-claude-kitHow this skill is triggered — by the user, by Claude, or both
Slash command
/dotnet-claude-kit:api-versioningThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
1. **Version from day one** — Adding versioning later is painful. Start with a version in the URL even if you only have v1.
Implements API versioning strategies in ASP.NET Core using Asp.Versioning library, supporting URL segments, headers, query strings, deprecation, and OpenAPI integration.
Versioning HTTP APIs. Asp.Versioning.Http/Mvc, URL segment, header, query string, sunset.
Guides API versioning strategies including URL path, header, query parameter, and content negotiation. Helps manage breaking changes, deprecations, and multiple versions.
Share bugs, ideas, or general feedback.
/api/v1/orders is the most discoverable and cache-friendly strategy.// Program.cs
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
})
.AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
var v1 = app.NewApiVersionSet()
.HasApiVersion(new ApiVersion(1, 0))
.Build();
var v2 = app.NewApiVersionSet()
.HasApiVersion(new ApiVersion(2, 0))
.Build();
app.MapGroup("/api/v{version:apiVersion}/orders")
.WithApiVersionSet(v1)
.WithTags("Orders")
.MapOrderEndpointsV1();
app.MapGroup("/api/v{version:apiVersion}/orders")
.WithApiVersionSet(v2)
.WithTags("Orders")
.MapOrderEndpointsV2();
options.ApiVersionReader = new HeaderApiVersionReader("X-Api-Version");
// Client sends: X-Api-Version: 2.0
var v1 = app.NewApiVersionSet()
.HasDeprecatedApiVersion(new ApiVersion(1, 0))
.HasApiVersion(new ApiVersion(2, 0))
.Build();
// Response headers will include: api-deprecated-versions: 1.0
public static class OrderEndpointsV1
{
public static RouteGroupBuilder MapOrderEndpointsV1(this RouteGroupBuilder group)
{
group.MapGet("/{id:guid}", GetOrderV1);
group.MapPost("/", CreateOrderV1);
return group;
}
private static async Task<Results<Ok<OrderResponseV1>, NotFound>> GetOrderV1(
Guid id, ISender sender, CancellationToken ct)
{
// V1 response shape
var result = await sender.Send(new GetOrder.Query(id), ct);
return result.IsSuccess
? TypedResults.Ok(result.Value.ToV1())
: TypedResults.NotFound();
}
}
public static class OrderEndpointsV2
{
public static RouteGroupBuilder MapOrderEndpointsV2(this RouteGroupBuilder group)
{
group.MapGet("/{id:guid}", GetOrderV2);
group.MapPost("/", CreateOrderV2);
return group;
}
private static async Task<Results<Ok<OrderResponseV2>, NotFound>> GetOrderV2(
Guid id, ISender sender, CancellationToken ct)
{
// V2 response shape — includes new fields
var result = await sender.Send(new GetOrder.Query(id), ct);
return result.IsSuccess
? TypedResults.Ok(result.Value.ToV2())
: TypedResults.NotFound();
}
}
// BAD — inconsistent versioning within a group
app.MapGet("/api/v1/orders", ListOrdersV1);
app.MapGet("/api/v2/orders/{id}", GetOrderV2); // V2 only for this endpoint?
// GOOD — version the entire group
app.MapGroup("/api/v1/orders").MapOrderEndpointsV1();
app.MapGroup("/api/v2/orders").MapOrderEndpointsV2();
// BAD for REST APIs — version hidden in query string, not cache-friendly
GET /api/orders?api-version=2.0
// GOOD — version in URL, discoverable and cacheable
GET /api/v2/orders
| Scenario | Recommendation |
|---|---|
| New public API | URL segment versioning from day one |
| Internal API between services | Header versioning (cleaner URLs) |
| Breaking response shape change | New version |
| Adding new optional fields | Same version (backwards compatible) |
| Deprecating a version | Mark deprecated, set sunset date, document migration path |