As APIs evolve, new features, bug fixes, and breaking changes become inevitable. Without a versioning strategy, modifying an existing endpoint can break existing clients, mobile applications, or third-party integrations.
API versioning enables multiple API versions to coexist, allowing consumers to migrate at their own pace while maintaining backward compatibility.
In this article, you'll learn how to implement API versioning in ASP.NET Core 11, explore common versioning approaches, and understand how to manage API evolution in production.
Note: This article focuses on implementation patterns and production practices. It does not include benchmark results because API versioning primarily affects maintainability and compatibility rather than raw performance.
Why API Versioning Matters
Suppose version 1 of an API returns:
{
"id": 1,
"name": "Laptop"
}
Later, version 2 introduces breaking changes.
{
"productId": 1,
"productName": "Laptop",
"price": 999
}
Without versioning, existing applications may fail because the response contract has changed.
Versioning allows both responses to remain available until consumers migrate.
Common API Versioning Strategies
| Strategy | Example | Best For |
|---|
| URL Path | /api/v1/products | Public REST APIs |
| Query String | /api/products?api-version=1.0 | Internal APIs |
| Header | api-version: 1.0 | Enterprise APIs |
| Media Type | application/json;v=1 | Advanced REST APIs |
URL versioning is the most common because it is simple, explicit, and easy to document.
Create the Project
dotnet new webapi -n ApiVersioningDemo
Install the versioning package.
dotnet add package Asp.Versioning.Mvc
Configure API Versioning
Register versioning services.
using Asp.Versioning;
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion =
new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
});
This configuration:
Uses version 1.0 by default
Reports supported versions
Supports requests without an explicit version (if configured)
URL Path Versioning
Create a versioned controller.
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/products")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("API Version 1");
}
}
Create version 2.
[ApiController]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/products")]
public class ProductsV2Controller : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("API Version 2");
}
}
Clients can now request:
GET /api/v1/products
GET /api/v2/products
Query String Versioning
Enable query string versioning.
builder.Services.AddApiVersioning(options =>
{
options.ApiVersionReader =
new QueryStringApiVersionReader(
"api-version");
});
Example request:
GET /api/products?api-version=2.0
This approach keeps the URL unchanged while allowing clients to specify a version.
Header Versioning
Read the version from a request header.
builder.Services.AddApiVersioning(options =>
{
options.ApiVersionReader =
new HeaderApiVersionReader(
"api-version");
});
Example:
GET /api/products
api-version: 2.0
Header versioning is common in enterprise environments where clean URLs are preferred.
Deprecating an API Version
Mark an older version as deprecated.
[ApiVersion("1.0", Deprecated = true)]
Clients receive version information through response headers when ReportApiVersions is enabled, helping them plan migrations.
Version-Specific Endpoints
Some endpoints may exist only in newer versions.
Version 1:
[HttpGet]
public IActionResult GetProducts()
{
return Ok();
}
Version 2:
[HttpGet]
public IActionResult GetProducts()
{
return Ok(new
{
Version = "2",
SupportsFiltering = true
});
}
New functionality can be introduced without breaking existing clients.
API Documentation
If you're using Swagger/OpenAPI, generate separate documentation for each API version.
Example structure:
/swagger/v1/swagger.json
/swagger/v2/swagger.json
Separate documentation makes it easier for consumers to understand the capabilities of each version.
End-to-End Request Flow
A versioned request typically follows these steps:
Client sends a request.
ASP.NET Core determines the requested API version.
The routing system selects the matching controller or action.
Business logic executes.
A version-specific response is returned.
Response headers indicate supported and deprecated versions (if configured).
This process enables multiple API versions to coexist within the same application.
Versioning Strategy Comparison
| Strategy | Readability | Client Friendly | REST Friendly | Recommended |
|---|
| URL Path | Excellent | Excellent | Yes | Yes |
| Query String | Good | Good | Moderate | Yes |
| Header | Moderate | Good | Yes | Enterprise APIs |
| Media Type | Advanced | Moderate | Yes | Specialized APIs |
Migration Strategy
A successful API evolution process typically includes:
Introduce a New Version
Create a new API version without modifying existing endpoints.
Support Both Versions
Allow clients time to migrate by keeping previous versions operational.
Mark Older Versions as Deprecated
Communicate deprecation through documentation and response headers.
Remove Deprecated Versions
After the announced support period ends, retire obsolete versions in a controlled manner.
Best Practices
Define an API versioning strategy before the first production release.
Avoid breaking changes within the same API version.
Deprecate versions before removing them.
Maintain separate API documentation for each version.
Keep response contracts consistent within a version.
Version only when changes are breaking.
Communicate version lifecycle clearly to API consumers.
Monitor usage of older API versions before retirement.
Common Mistakes
| Mistake | Impact |
|---|
| Changing response contracts without versioning | Broken clients |
| Supporting too many versions indefinitely | Higher maintenance cost |
| Removing old versions without notice | Service disruption |
| Mixing versioning strategies inconsistently | Confusing API design |
| Failing to document differences | Poor developer experience |
| Introducing breaking changes in minor updates | Compatibility issues |
Troubleshooting
Incorrect Controller Is Selected
Verify:
Clients Receive Unsupported Version Errors
Review:
Swagger Shows Only One Version
Ensure separate Swagger documents are generated and mapped for each API version.
FAQs
Which versioning strategy is most common?
URL path versioning is widely used because it is simple, explicit, and easy to understand.
When should I create a new API version?
Create a new version whenever you introduce breaking changes to request or response contracts.
Should every new feature require a new version?
No. Backward-compatible additions can usually be introduced within the existing version.
How long should old API versions be supported?
The support period depends on your organization's API lifecycle policy and client migration requirements.
Can multiple API versions run in the same application?
Yes. ASP.NET Core supports hosting multiple API versions simultaneously, allowing gradual client migration.
Conclusion
API versioning is essential for maintaining backward compatibility while allowing APIs to evolve over time. A well-planned versioning strategy minimizes disruptions, improves the developer experience, and enables clients to adopt new features at their own pace.
By selecting an appropriate versioning approach, documenting changes clearly, and managing deprecation carefully, you can build ASP.NET Core APIs that remain stable, maintainable, and production-ready as they grow.