Isaac.

API Versioning in ASP.NET Core

Learn about different strategies for API versioning in ASP.NET Core.

By EMEPublished: January 5, 2024
aspnetapiversioning

API Versioning in ASP.NET Core

A comprehensive guide to implementing API versioning strategies in ASP.NET Core applications.

Why API Versioning Matters

As your API evolves, you will change data shapes, endpoints, or behavior. Without versioning, you risk breaking existing clients. Versioning gives you a controlled way to introduce changes while continuing to support older clients.

Common Versioning Strategies

1. URL Path Versioning

In URL path versioning, the version number is included in the API endpoint URL. This approach is explicit and makes it clear which version of the API is being accessed.

GET /api/v1/users
GET /api/v2/users

Pros:

  • Discoverability: The API version is immediately visible in the URL
  • Simplicity: Straightforward to implement and test
  • Cache-friendly: Different versions have different URLs

Cons:

  • URL proliferation: Multiple versions mean multiple endpoints
  • Resource duplication: Similar logic across versions

2. Header Versioning

Header versioning uses HTTP headers to specify the API version, typically through an X-API-Version header or custom media types.

GET /api/users
X-API-Version: 2

Pros:

  • Clean URLs: Same URL for all versions
  • Flexibility: Clients can negotiate versions without changing URLs
  • RESTful: Follows HTTP standards for content negotiation

Cons:

  • Less discoverable: Version not visible in the URL
  • Harder to test manually (requires headers)

3. Query Parameter Versioning

The version is passed as a query parameter in the request.

GET /api/users?v=2

Pros:

  • Simple to implement
  • Easy to test in browsers

Cons:

  • Can cause caching issues
  • Less RESTful approach
  • Less explicit in code

4. Content Negotiation (Media Type)

Use custom Accept header media types to specify the version.

GET /api/users
Accept: application/vnd.myapp.v2+json

Pros:

  • Highly RESTful
  • Follows HTTP standards
  • Clean URLs

Cons:

  • More complex to implement
  • Less discoverable for developers

Implementation in ASP.NET Core

Using API Versioning NuGet Package

The easiest approach is to use the Asp.Versioning.Mvc NuGet package.

dotnet add package Asp.Versioning.Mvc

Setup in Program.cs

using Asp.Versioning;

var builder = WebApplicationBuilder.CreateBuilder(args);

// Add API versioning
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.ReportApiVersions = true;
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ApiVersionReader = ApiVersionReader.Combine(
        new UrlSegmentApiVersionReader(),
        new HeaderApiVersionReader("X-API-Version")
    );
})
.AddApiExplorer(options =>
{
    options.GroupNameFormat = "'v'VVV";
    options.SubstituteApiVersionInUrl = true;
});

builder.Services.AddControllers();

var app = builder.Build();

app.MapControllers();
app.Run();

Version by URL Path

Create controllers with the [ApiVersion] attribute:

[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
public class UsersController : ControllerBase
{
    [HttpGet]
    public IActionResult GetUsers()
    {
        return Ok(new[] 
        { 
            new { id = 1, name = "Alice" },
            new { id = 2, name = "Bob" }
        });
    }
}

[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("2.0")]
public class UsersV2Controller : ControllerBase
{
    [HttpGet]
    public IActionResult GetUsers()
    {
        return Ok(new[]
        {
            new { id = 1, name = "Alice", email = "alice@example.com", roles = new[] { "Admin" } },
            new { id = 2, name = "Bob", email = "bob@example.com", roles = new[] { "User" } }
        });
    }
}

Version by Header

[ApiController]
[Route("api/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    public IActionResult GetProducts()
    {
        var apiVersion = HttpContext.GetRequestedApiVersion();
        
        if (apiVersion?.MajorVersion == 2)
        {
            return Ok(new[]
            {
                new { id = 1, name = "Product A", price = 29.99m, stock = 100 },
                new { id = 2, name = "Product B", price = 49.99m, stock = 50 }
            });
        }

        return Ok(new[]
        {
            new { id = 1, name = "Product A", price = 29.99m },
            new { id = 2, name = "Product B", price = 49.99m }
        });
    }
}

Deprecating API Versions

Use the Deprecated property to mark versions as no longer supported:

[ApiVersion("1.0", Deprecated = true)]
[ApiVersion("2.0")]
public class OrdersController : ControllerBase
{
    // Implementation
}

Migration Strategies

  1. Announce Early: Give clients advance notice before deprecating versions
  2. Support Window: Keep old versions alive for 6-12 months after deprecation
  3. Gradual Migration: Provide migration guides for clients
  4. Sunset Headers: Include deprecation headers in responses:
Response.Headers.Add("Sunset", "Sun, 01 Jan 2025 00:00:00 GMT");
Response.Headers.Add("Deprecated", "true");
  1. Monitor Usage: Track which API versions clients are using
  2. Clear Communication: Document breaking changes clearly

Best Practices

  • Be consistent: Choose one versioning strategy and stick with it
  • Version early: Start with versioning from day one
  • Avoid minor versions in URLs: Use major versions for breaking changes
  • Document thoroughly: Explain differences between versions
  • Minimize duplication: Use inheritance or composition to share logic
  • Monitor deprecated versions: Know when clients have migrated
  • Test all versions: Ensure each version works as expected

Conclusion

API versioning is essential for maintaining backward compatibility while evolving your API. ASP.NET Core provides excellent tooling through the Asp.Versioning NuGet package to implement any versioning strategy effectively.

Choose the strategy that best fits your use case:

  • Use URL path versioning for maximum clarity and simplicity
  • Use header versioning for RESTful APIs with stable URLs
  • Use media type negotiation for highly RESTful implementations

Plan your versioning strategy early and communicate deprecations clearly to your clients.