When you change your API's JSON response structure, you break every mobile app currently installed on users' phones. To prevent this, APIs must be versioned. You leave the old API (v1) running for legacy clients, and build the new features in a separate API structure (v2).
ASP.NET Core supports enterprise-grade versioning via the Asp.Versioning.Mvc package.
dotnet add package Asp.Versioning.Mvc
builder.Services.AddApiVersioning(options =>
{
// If client doesn't specify a version, assume they want v1.0
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
// Returns headers showing which versions are supported/deprecated
options.ReportApiVersions = true;
});
You define multiple controller classes and tag them with their specific version. Both controllers can exist simultaneously without causing an AmbiguousMatchException.
// OLD CONTROLLER
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/users")]
public class UsersV1Controller : ControllerBase
{
[HttpGet] // URL: GET /api/v1/users
public IActionResult GetUsers() {
return Ok(new[] { new { Name = "John" } }); // Returns flat JSON
}
}
// NEW CONTROLLER (Breaking Change!)
[ApiController]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/users")]
public class UsersV2Controller : ControllerBase
{
[HttpGet] // URL: GET /api/v2/users
public IActionResult GetUsers() {
// We added a data wrapper and pagination metadata! (Breaking change!)
return Ok(new { data = new[] { new { FirstName = "John" } }, totalCount = 1 });
}
}
URL Versioning (shown above) is just one way to communicate the requested version. The framework supports three distinct strategies.
GET /api/v2/users
The most common and explicitly readable method. Best for public REST APIs used by third-party developers.
GET /api/users?api-version=2.0
Keeps the main URL clean, but often gets lost or stripped by aggressive caching proxies.
Header: x-api-version: 2.0
The most "RESTful" approach (URLs shouldn't change just because data shape changes), but difficult to test directly in a browser without Postman.
Q: "When is a change considered a 'Breaking Change' requiring an entirely new API Version, versus a safe change that can just be updated silently on v1?"
Architect Answer: "The rule of thumb relies on Postel's Law (The Robustness Principle). A 'Safe' change is an additive change. If I add a new `Age` property to the JSON response, older clients will simply ignore the new JSON field because they weren't programmed to look for it. No version required. A 'Breaking' change is a reductive or mutating change. If I rename `FirstName` to `Name`, delete a property entirely, change a property's type from a string to an integer, or require a brand new mandatory HTTP Header that wasn't previously required, the mobile app will immediately crash when attempting to parse the JSON. That requires a brand new v2 API endpoint."