Skip to content
Frameworks

.NET API - Versioning and Best Practices

Published on:
·
Updated on:
·Author: MDS Software Solutions Group
.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!

Author
MDS Software Solutions Group

Team of programming experts specializing in modern web technologies.

.NET API - Versioning and Best Practices | MDS Software Solutions Group | MDS Software Solutions Group