API versioning allows you to evolve your API without breaking existing clients. Once an API is in production and clients depend on it, any change to the response shape, required parameters, or behaviour is potentially breaking. Versioning gives you a controlled way to introduce changes: maintain the old version for existing clients while new clients adopt the new version. The choice of versioning strategy — URL path, query string, or header — affects discoverability, caching, and how Angular clients implement version selection.
Configuring API Versioning
// dotnet add package Asp.Versioning.Mvc
// dotnet add package Asp.Versioning.Mvc.ApiExplorer (for Swagger integration)
// ── Program.cs — configure versioning ─────────────────────────────────────
builder.Services
.AddApiVersioning(opts =>
{
opts.DefaultApiVersion = new ApiVersion(1, 0);
opts.AssumeDefaultVersionWhenUnspecified = true; // v1 if no version specified
opts.ReportApiVersions = true; // add Api-Supported-Versions header
// Support multiple version reading strategies simultaneously
opts.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(), // /api/v1/posts
new QueryStringApiVersionReader("api-version"), // ?api-version=1.0
new HeaderApiVersionReader("Api-Version")); // Api-Version: 1.0
})
.AddMvc()
.AddApiExplorer(opts =>
{
opts.GroupNameFormat = "'v'VVV"; // v1, v1.1, v2
opts.SubstituteApiVersionInUrl = true; // replaces {version} token in routes
});
// ── URL path versioning — most explicit ───────────────────────────────────
// /api/v1/posts ← Version 1
// /api/v2/posts ← Version 2 (new schema)
// ── Query string versioning — backwards compatible ────────────────────────
// /api/posts ← uses default version (v1)
// /api/posts?api-version=2.0 ← explicitly requests v2
// ── Header versioning — clean URLs, less discoverable ─────────────────────
// GET /api/posts
// Api-Version: 2.0
// ── Media type versioning — REST purist approach ──────────────────────────
// Accept: application/vnd.blogapp.v2+json
builder.Services.AddApiVersioning(opts =>
{
opts.ApiVersionReader = new MediaTypeApiVersionReader("ver");
// Accept: application/json;ver=2.0
});
/api/v1/posts) is the most discoverable and widely recommended approach. It is visible in URLs (appears in browser history, logs, documentation), works naturally with CDN caching (different URLs cache independently), and makes the version explicit in every API call. Header and query string versioning keep URLs clean but require clients to always set extra metadata. For public APIs consumed by Angular clients and third-party developers, URL versioning is the safest default choice.v1 → v2), minor version changes for additive non-breaking additions (v1.0 → v1.1). With Asp.Versioning, you can express minor versions as ApiVersion(1, 1) and match them with [ApiVersion("1.1")]. Route them as /api/v1.1/posts. Minor versions let you add features without forcing all clients to migrate to a major version.AssumeDefaultVersionWhenUnspecified = true means requests without a version specifier silently use the default version. This is convenient for migration but can mask version detection failures. In a production API where clients should always specify a version, consider setting it to false after the initial migration period — requiring explicit versioning forces clients to make a deliberate version choice rather than silently pinning to the default.Version Reader Response Headers
// ── With ReportApiVersions = true, every response includes: ───────────────
// Api-Supported-Versions: 1.0, 2.0
// Api-Deprecated-Versions: 1.0 (when version is marked deprecated)
// ── Deprecation headers (added by Asp.Versioning automatically) ───────────
[ApiController]
[ApiVersion("1.0", Deprecated = true)] // v1 is deprecated
[ApiVersion("2.0")] // v2 is current
[Route("api/v{version:apiVersion}/posts")]
public class PostsController : ControllerBase { }
// ── Automatic deprecation headers on v1 responses: ────────────────────────
// Deprecation: true
// Sunset: Sat, 01 Jun 2026 00:00:00 GMT (configurable sunset date)
// Link: <https://api.blogapp.com/api/v2/posts>; rel="successor-version"
// ── Angular client reading version headers ────────────────────────────────
// In the HTTP interceptor:
// if (response.headers.get('Deprecation')) {
// console.warn('API version deprecated. Please upgrade to v2.');
// }
Common Mistakes
Mistake 1 — No default version set (requests without version header return 400)
❌ Wrong — existing clients without version header suddenly start failing after versioning is introduced.
✅ Correct — set DefaultApiVersion = new ApiVersion(1, 0) and AssumeDefaultVersionWhenUnspecified = true for backwards compatibility.
Mistake 2 — Never deprecating or removing old versions (infinite version maintenance)
❌ Wrong — v1, v2, v3, v4 all maintained indefinitely; each new feature must be implemented in all versions.
✅ Correct — mark old versions as deprecated with a sunset date; announce the timeline; remove after sunset.