Introduction
Imagine you have built an ASP.NET Core Web API that several applications depend on. Your mobile app uses it to retrieve customer details, your website uses it to display orders, and another service uses it to process payments.
Everything works fine until you need to change the API response. Perhaps you want to rename a property, remove an old field, or change how an endpoint accepts data.
If you make these changes directly, existing applications may stop working. Updating every client at the same time is not always possible, especially when different teams maintain different applications.
This is where API versioning becomes useful.
API versioning allows you to introduce changes without immediately breaking existing clients. You can keep the current API available while developing a newer version with updated behavior.
In this article, we will learn how to add API versioning to an ASP.NET Core Web API, create separate versions of an endpoint, and handle common problems when using versioning in a real application.
What Is API Versioning?
API versioning is a way to maintain multiple versions of an API so that clients can continue using the version they depend on.
For example, consider an API that returns customer information.
Version 1 might return the customer's full name in a single property:
{
"id": 101,
"fullName": "Rahul Sharma"
}Later, you may want to return the first and last names separately.
{
"id": 101,
"firstName": "Rahul",
"lastName": "Sharma"
}If you change the existing response without considering current clients, applications expecting fullName may fail.
With API versioning, you can keep the original response available in version 1 and introduce the new response in version 2.
This gives clients time to migrate without forcing every application to update immediately.
Why Do You Need API Versioning?
API versioning is particularly useful when an API is shared across multiple applications or teams.
Here are some common situations where it helps:
- You need to change the structure of a response.
- You want to remove an old property or endpoint.
- You need to introduce new request parameters.
- Different clients need different API behavior.
- You want to introduce breaking changes without disrupting existing integrations.
Not every API change requires a new version. Adding an optional response field, for example, may be compatible with existing clients. The decision depends on how clients consume the API and the compatibility guarantees you provide.
Step 1: Create an ASP.NET Core Web API
Start by creating an ASP.NET Core Web API project using your preferred development environment.
You can create a project from the command line:
dotnet new webapi -n VersionedApi
cd VersionedApiNext, install the API versioning packages.
For controller-based APIs, install the following packages:
dotnet add package Asp.Versioning.Mvc
dotnet add package Asp.Versioning.Mvc.ApiExplorerThe first package provides API versioning support for MVC controllers. The second integrates version information with API Explorer, which is useful for generating version-specific API documentation.
Use package versions compatible with your target .NET framework and the other dependencies in your project.
Step 2: Configure API Versioning
Open Program.cs and register API versioning services.
In this example, we will use URL path versioning, where the version appears in the request URL.
using Asp.Versioning;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services
.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = false;
options.ReportApiVersions = true;
options.ApiVersionReader =
new UrlSegmentApiVersionReader();
})
.AddMvc()
.AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
var app = builder.Build();
app.UseHttpsRedirection();
app.MapControllers();
app.Run();Let's look at the important settings.
DefaultApiVersion defines the default version value used by the versioning system.
AssumeDefaultVersionWhenUnspecified is set to false so clients must provide a version instead of silently receiving the default version.
ReportApiVersions enables response headers that report supported and deprecated API versions where applicable.
UrlSegmentApiVersionReader tells ASP.NET Core to read the version from the URL path.
For example:
/api/v1/products
/api/v2/productsThe API Explorer configuration helps organize endpoints by version when you integrate a compatible OpenAPI or Swagger setup.
Step 3: Create a Versioned Controller
Now, create a controller that supports version 1.
Create a file named ProductsController.cs.
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
namespace VersionedApi.Controllers;
[ApiController]
[ApiVersion(1.0)]
[Route("api/v{version:apiVersion}/products")]
public class ProductsV1Controller : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
var products = new[]
{
new
{
Id = 1,
Name = "Laptop",
Price = 75000
},
new
{
Id = 2,
Name = "Keyboard",
Price = 2500
}
};
return Ok(products);
}
}The [ApiVersion(1.0)] attribute identifies the version supported by this controller.
The route contains v{version:apiVersion}, which allows the API versioning system to recognize the version segment in the URL.
When a client sends a GET request to /api/v1/products, the version 1 controller handles the request.
The example uses sample data to keep the focus on versioning. In a real application, you would usually retrieve products through a service or database.
Step 4: Add Version 2 of the API
Suppose you need to change the product response. Version 1 returns Name, while version 2 should return ProductName and include an availability status.
You can create a separate controller for version 2.
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
namespace VersionedApi.Controllers;
[ApiController]
[ApiVersion(2.0)]
[Route("api/v{version:apiVersion}/products")]
public class ProductsV2Controller : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
var products = new[]
{
new
{
Id = 1,
ProductName = "Laptop",
Price = 75000,
IsAvailable = true
},
new
{
Id = 2,
ProductName = "Keyboard",
Price = 2500,
IsAvailable = true
}
};
return Ok(products);
}
}Both controllers use the same route pattern, but they support different API versions.
The versioning system selects the appropriate controller based on the version in the request URL.
For example, a client requesting /api/v1/products receives the version 1 response, while a client requesting /api/v2/products receives the version 2 response.
This lets you introduce breaking changes without immediately changing the response expected by existing clients.
Step 5: Test the API Versions
Run your application and test both endpoints using your preferred API testing tool.
HTTP Method | Endpoint | Expected result |
|---|---|---|
GET |
| Version 1 product response |
GET |
| Version 2 product response |
GET |
| No matching version unless version 3 is configured |
GET |
| Version is missing and is not assumed |
The exact error response for an unsupported or missing version can depend on the API versioning configuration and framework integration.
Testing both supported versions is important because a successful version 2 request does not prove that version 1 clients still receive the response they expect.
Common API Versioning Strategies
URL path versioning is only one option. The best approach depends on your API conventions and client requirements.
Strategy | Example | Advantages | Disadvantages |
|---|---|---|---|
URL path |
| Easy to read, test, and route | Creates distinct URLs for each version |
Query string |
| Simple to add to requests | Version information is less visible in the path |
Request header |
| Keeps version out of the URL | Requires clients to send the correct header |
Media type |
| Supports representation-based versioning | More complex to configure and troubleshoot |
Choose one strategy that fits your existing API design. Supporting several version-reading methods without a clear reason can make client behavior harder to understand.
Best Practices for Production Applications
1. Keep breaking changes in a new version
Avoid changing an existing response in a way that breaks clients unexpectedly. Introduce a new version when compatibility cannot be maintained.
2. Share business logic between versions
Different API versions do not necessarily need separate business logic.
Keep shared operations in application services and let each controller handle the version-specific request and response formats. This reduces duplication and makes maintenance easier.
3. Document each supported version
Maintain separate API documentation for each version so developers know which endpoints, request formats, and response properties are available.
If you use Swagger or another OpenAPI tool, configure it to generate documentation for the appropriate API version groups.
4. Plan version retirement
Older versions should not remain available indefinitely without a reason.
Communicate retirement dates, give consumers enough time to migrate, and monitor usage before removing an older version. The appropriate support period depends on your clients and contractual requirements.
5. Test backward compatibility
Add automated tests that verify important response fields and behavior for every supported version.
For example, tests should confirm that version 1 still returns Name even after version 2 introduces ProductName.
6. Monitor version usage
Track which versions clients use and identify consumers that still depend on older endpoints. This information helps you plan migrations and avoid retiring versions that are still required.
Common Mistakes and Troubleshooting
The endpoint returns 404: Check the route template, the [ApiVersion] attribute, and the version in the request URL.
The API reports an unsupported version: Confirm that the requested version is registered on a matching controller and that the URL uses the expected format.
Both controllers conflict: Make sure the controllers declare the correct API versions and use compatible versioned route templates.
Swagger shows incorrect endpoints: Verify the API Explorer configuration and ensure your OpenAPI integration supports the versioning package you installed.
Old clients stop working: Review changes to existing response contracts and add compatibility tests before deploying a new version.
One important point: API versioning does not automatically make your API backward-compatible. You still need to preserve old contracts, test behavior, and manage version retirement carefully.
Advantages and Disadvantages of API Versioning
Advantages
- Allows breaking changes without immediately disrupting existing clients.
- Supports gradual migration between API contracts.
- Makes it easier to maintain different client requirements.
- Helps teams introduce new features with a clear compatibility boundary.
Disadvantages
- Requires additional testing and documentation.
- Can increase maintenance effort when several versions remain active.
- May lead to duplicated controller code if shared logic is not designed properly.
- Requires a clear policy for deprecation and retirement.
Summary
API versioning helps you evolve an ASP.NET Core Web API without forcing every client to adopt changes at the same time.
With the Asp.Versioning.Mvc package, you can configure version handling, assign versions to controllers, and expose different response contracts through versioned endpoints.
URL path versioning is a straightforward choice for many APIs because the version is visible in each request. However, the most important part is not the versioning strategy itself. It is maintaining stable contracts, testing existing clients, documenting changes, and planning when older versions will be retired.
Start with a clear versioning policy, keep shared business logic separate from version-specific controllers, and introduce new versions only when they provide a real compatibility benefit.

Join the conversation! Your thoughts help the community grow.