Tuesday, 23 September 2025

Web API Versioning

1️⃣ Why API Versioning Matters

Imagine you published a REST API for mobile apps:

GET /api/products
  • Later you need a new field or change a parameter.

  • You can’t break old mobile apps that still call the old format.

API versioning lets you:

  • Publish new versions of your API

  • Keep old versions running until clients migrate.


2️⃣ Common Ways to Show the Version

You must tell the server which version the client wants.
Popular options:

ApproachExample callProsCons
URL path/api/v1/productsVery clear, cache friendlyURL changes with every version
Query string/api/products?api-version=1.0Easy to testLess obvious in logs
HTTP headerGET /api/products + x-api-version:1.0Clean URLsHarder to debug in browser
Accept header (media type)Accept: application/json; v=1.0Follows REST best practiceRequires custom header setup

You can even combine them.


3️⃣ Official Microsoft Library

Use the NuGet package:

Microsoft.AspNetCore.Mvc.Versioning

This library makes versioning easy.


4️⃣ Basic Setup (Path-based Versioning)

1. Install Package

dotnet add package Microsoft.AspNetCore.Mvc.Versioning

2. Configure in Program.cs (or Startup.cs)

builder.Services.AddApiVersioning(options => { options.AssumeDefaultVersionWhenUnspecified = true; options.DefaultApiVersion = new ApiVersion(1, 0); options.ReportApiVersions = true; // adds headers: api-supported-versions });
  • AssumeDefaultVersionWhenUnspecified: if client doesn’t pass a version, use default (1.0).

  • ReportApiVersions: tells clients which versions are available.

3. Version Your Controllers

[ApiVersion("1.0")] [Route("api/v{version:apiVersion}/[controller]")] [ApiController] public class ProductsController : ControllerBase { [HttpGet] public string GetV1() => "This is V1"; }

Add a second version:

[ApiVersion("2.0")] [Route("api/v{version:apiVersion}/[controller]")] [ApiController] public class ProductsV2Controller : ControllerBase { [HttpGet] public string GetV2() => "This is V2 with more fields"; }

Now clients call:

/api/v1/products /api/v2/products

5️⃣ Query-String Versioning

If you prefer ?api-version=1.0:

builder.Services.AddApiVersioning(options => { options.ApiVersionReader = new QueryStringApiVersionReader("api-version"); });

Then the route can simply be [Route("api/[controller]")].
Clients call:

/api/products?api-version=1.0

6️⃣ Header-Based Versioning

builder.Services.AddApiVersioning(options => { options.ApiVersionReader = new HeaderApiVersionReader("x-api-version"); });

Client adds header:

x-api-version: 2.0

7️⃣ Multiple Versions in One Controller

You can keep both actions in the same file:

[ApiVersion("1.0")] [ApiVersion("2.0")] [Route("api/products")] [ApiController] public class ProductsController : ControllerBase { [HttpGet, MapToApiVersion("1.0")] public string GetV1() => "Old result"; [HttpGet, MapToApiVersion("2.0")] public string GetV2() => "New result"; }

8️⃣ Deleting or Deprecating Versions

Mark an old version as deprecated:

[ApiVersion("1.0", Deprecated = true)]

The response header api-deprecated-versions warns clients.


9️⃣ Best Practices

  1. Choose one clear versioning style and stick to it.

  2. Document which versions exist and when they’ll be retired.

  3. Use semantic versioning (1.0, 1.1, 2.0) to show backward compatibility.

  4. Automate tests for every active version.

  5. When possible, design for backward compatibility to avoid breaking clients.


🔟 Quick Flow Recap

  1. Client requests with a version (URL / query / header).

  2. ASP.NET Core checks the version using the configured reader.

  3. The correct controller/action for that version executes.

  4. Response headers tell the client which versions are supported.


🧠 Key Takeaways

  • API versioning keeps old clients working while you release improvements.

  • Microsoft.AspNetCore.Mvc.Versioning makes it almost plug-and-play.

  • Pick a strategy (URL path, query, or header) and stay consistent.

  • Mark old versions as deprecated before removal.