.NET API - Versioning and Best Practices

.NET API: Versioning and Best Practices
API versioning is a crucial aspect of enterprise application development. It allows introducing changes without breaking existing clients. This article presents a comprehensive guide to API versioning in .NET using ASP.NET Core.
Why is API Versioning Important?
As applications evolve, APIs change:
- New features - adding new endpoints
- Breaking changes - modifying contracts
- Optimization - improving performance
- Deprecation - phasing out old features
- Backward compatibility - supporting older clients
API Versioning Strategies
1. URL Path Versioning
Most popular method - version in URL path:
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class ProductsController : ControllerBase
{
[HttpGet]
[MapToApiVersion("1.0")]
public ActionResult<IEnumerable<ProductV1>> GetV1()
{
return Ok(new[] { new ProductV1 { Id = 1, Name = "Product" } });
}
[HttpGet]
[MapToApiVersion("2.0")]
public ActionResult<IEnumerable<ProductV2>> GetV2()
{
return Ok(new[] {
new ProductV2 {
Id = 1,
Name = "Product",
Description = "New field in v2"
}
});
}
}
Pros:
- Easy to use and understand
- Visible in URL
- Good for documentation
- Cache-friendly
Cons:
- URL can become long
- Need to duplicate code for each version
2. Header Versioning
Version in HTTP header:
[ApiController]
[Route("api/products")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class ProductsController : ControllerBase
{
[HttpGet]
[MapToApiVersion("1.0")]
public ActionResult<IEnumerable<ProductV1>> GetV1() { }
[HttpGet]
[MapToApiVersion("2.0")]
public ActionResult<IEnumerable<ProductV2>> GetV2() { }
}
Client sends: api-version: 2.0 header
3. Query String Versioning
Version as query parameter:
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
[HttpGet]
[MapToApiVersion("1.0")]
public ActionResult<IEnumerable<ProductV1>> GetV1() { }
}
Call: GET /api/products?api-version=2.0
Configuration in ASP.NET Core
Package Installation
dotnet add package Asp.Versioning.Mvc
dotnet add package Asp.Versioning.Mvc.ApiExplorer
Basic Configuration
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
}).AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
var app = builder.Build();
app.MapControllers();
app.Run();
Best Practices
1. Semantic Versioning
Use semantic versioning (MAJOR.MINOR.PATCH):
[ApiVersion("1.0")] // Initial release
[ApiVersion("1.1")] // New features, backward compatible
[ApiVersion("2.0")] // Breaking changes
2. API Version Deprecation
Mark deprecated versions:
[ApiVersion("1.0", Deprecated = true)]
[ApiVersion("2.0")]
public class ProductsController : ControllerBase
{
[HttpGet]
[MapToApiVersion("1.0")]
[Obsolete("Use v2.0 instead")]
public ActionResult<ProductV1> GetV1()
{
Response.Headers.Add("X-API-Warn",
"This version is deprecated. Use v2.0");
return Ok(/* ... */);
}
}
3. Documentation with Swagger
Swagger integration for each version:
builder.Services.AddSwaggerGen(options =>
{
var provider = builder.Services.BuildServiceProvider()
.GetRequiredService<IApiVersionDescriptionProvider>();
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerDoc(
description.GroupName,
new OpenApiInfo
{
Title = $"My API {description.ApiVersion}",
Version = description.ApiVersion.ToString(),
Description = description.IsDeprecated
? "This API version is deprecated"
: ""
});
}
});
Testing Different Versions
public class ProductsControllerTests
{
[Fact]
public async Task GetV1_ReturnsProductsWithoutDescription()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/api/v1/products");
response.EnsureSuccessStatusCode();
var products = await response.Content
.ReadFromJsonAsync<List<ProductV1>>();
Assert.NotNull(products);
}
[Fact]
public async Task GetV2_ReturnsProductsWithDescription()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/api/v2/products");
response.EnsureSuccessStatusCode();
var products = await response.Content
.ReadFromJsonAsync<List<ProductV2>>();
Assert.All(products, p => Assert.NotNull(p.Description));
}
}
API Versioning Checklist
Before deploying versioning, ensure:
- [ ] Versioning strategy selected (URL/Header/Query)
- [ ] Semantic versioning applied
- [ ] Default version configured
- [ ] Deprecated versions marked
- [ ] Swagger documentation for each version
- [ ] Tests for all versions
- [ ] Breaking changes documented
- [ ] Migration guide prepared
- [ ] Per-version monitoring enabled
- [ ] Sunset policy defined
- [ ] Client libraries updated
- [ ] Client communication about changes
Version Migration
Sunset Header
Inform clients about version retirement:
[ApiVersion("1.0", Deprecated = true)]
public class ProductsController : ControllerBase
{
[HttpGet]
[MapToApiVersion("1.0")]
public ActionResult<ProductV1> GetV1()
{
Response.Headers.Add("Sunset",
"Sat, 31 Dec 2024 23:59:59 GMT");
Response.Headers.Add("Link",
"</api/v2/products>; rel=\"successor-version\"");
return Ok(/* ... */);
}
}
Summary
Good API versioning in .NET requires:
- Clear strategy - choose method and stick to it
- Documentation - Swagger for each version
- Tests - coverage for all versions
- Communication - inform clients about changes
- Gradual deprecation - give time for migration
Proper versioning allows API evolution without breaking existing integrations.
Need Help with APIs?
At MDS Software Solutions Group, we offer:
- API architecture design
- Versioning implementation
- Existing API audit
- Migration to new versions
- 24/7 technical support
Contact us to discuss your project!
Team of programming experts specializing in modern web technologies.