As your API evolves, breaking changes are inevitable. Introducing API versioning ensures that your clients continue to work without disruption while you introduce new features and improvements.
This guide covers how to implement API versioning in .NET Web API with practical examples, including a clean BaseApiController approach for centralized configuration.
Why API Versioning?
- Backward Compatibility: Old clients continue to work with previous versions.
- Smooth Upgrades: You can introduce new endpoints without breaking existing functionality.
- Clear Communication: Clients know which version they are using.
- Better Lifecycle Management: Easier to deprecate old versions gradually.
Step 1: Add Required NuGet Package
dotnet add package Microsoft.AspNetCore.Mvc.Versioning
This package provides tools to version your API by query string, header, or URL segment.
Step 2: Configure Versioning in Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// Add API versioning
builder.Services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new Microsoft.AspNetCore.Mvc.ApiVersion(1, 0);
options.ReportApiVersions = true; // Returns API versions in response headers
});
var app = builder.Build();
app.MapControllers();
app.Run();
Step 3: Create a BaseApiController
A BaseApiController can hold common configuration, attributes, and helper methods for all versioned controllers.
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public abstract class BaseApiController : ControllerBase
{
protected IActionResult ApiResponse(object data, int statusCode = 200)
{
return StatusCode(statusCode, new
{
Status = statusCode,
Data = data,
Version = HttpContext.GetRequestedApiVersion()?.ToString()
});
}
}
All your versioned controllers can now inherit from this base controller for consistent behavior.
Step 4: Versioning Controllers Using BaseApiController
[ApiVersion("1.0")]
public class ProductsController : BaseApiController
{
[HttpGet]
public IActionResult Get() => ApiResponse(new[] { "Product 1", "Product 2" });
}
[ApiVersion("2.0")]
public class ProductsV2Controller : BaseApiController
{
[HttpGet]
public IActionResult Get() => ApiResponse(new[] { "Product A", "Product B", "Product C" });
}
This keeps your code DRY and centralizes versioning logic.
Step 5: Query String and Header-Based Versioning
Query string example:
GET /api/orders?api-version=1.0
GET /api/orders?api-version=2.0
Header-based configuration in Program.cs
:
builder.Services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ApiVersionReader = new HeaderApiVersionReader("x-api-version");
options.ReportApiVersions = true;
});
Clients can now set the header:
x-api-version: 2.0
Step 6: Deprecating Versions
[ApiVersion("1.0", Deprecated = true)]
public class OldController : BaseApiController
{
[HttpGet]
public IActionResult Get() => ApiResponse("This version is deprecated");
}
Headers will indicate deprecation.
Step 7: Swagger Integration for Versioned APIs
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "API v1", Version = "v1" });
options.SwaggerDoc("v2", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "API v2", Version = "v2" });
});
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("https://dev.to/swagger/v1/swagger.json", "API v1");
options.SwaggerEndpoint("https://dev.to/swagger/v2/swagger.json", "API v2");
});
Wrapping Up
Using a BaseApiController centralizes common logic, keeps controllers DRY, and makes managing API versioning cleaner. Combined with URL, query string, or header-based versioning and Swagger integration, this approach provides a flexible and maintainable API structure in .NET.