ASP.NET Core  

API Versioning Best Practices in ASP.NET Core 11

As APIs evolve, new features, bug fixes, and breaking changes become inevitable. Without a versioning strategy, modifying an existing endpoint can break existing clients, mobile applications, or third-party integrations.

API versioning enables multiple API versions to coexist, allowing consumers to migrate at their own pace while maintaining backward compatibility.

In this article, you'll learn how to implement API versioning in ASP.NET Core 11, explore common versioning approaches, and understand how to manage API evolution in production.

Note: This article focuses on implementation patterns and production practices. It does not include benchmark results because API versioning primarily affects maintainability and compatibility rather than raw performance.

Why API Versioning Matters

Suppose version 1 of an API returns:

{
  "id": 1,
  "name": "Laptop"
}

Later, version 2 introduces breaking changes.

{
  "productId": 1,
  "productName": "Laptop",
  "price": 999
}

Without versioning, existing applications may fail because the response contract has changed.

Versioning allows both responses to remain available until consumers migrate.

Common API Versioning Strategies

StrategyExampleBest For
URL Path/api/v1/productsPublic REST APIs
Query String/api/products?api-version=1.0Internal APIs
Headerapi-version: 1.0Enterprise APIs
Media Typeapplication/json;v=1Advanced REST APIs

URL versioning is the most common because it is simple, explicit, and easy to document.

Create the Project

dotnet new webapi -n ApiVersioningDemo

Install the versioning package.

dotnet add package Asp.Versioning.Mvc

Configure API Versioning

Register versioning services.

using Asp.Versioning;

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion =
        new ApiVersion(1, 0);

    options.AssumeDefaultVersionWhenUnspecified = true;

    options.ReportApiVersions = true;
});

This configuration:

  • Uses version 1.0 by default

  • Reports supported versions

  • Supports requests without an explicit version (if configured)

URL Path Versioning

Create a versioned controller.

[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/products")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Ok("API Version 1");
    }
}

Create version 2.

[ApiController]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/products")]
public class ProductsV2Controller : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Ok("API Version 2");
    }
}

Clients can now request:

GET /api/v1/products

GET /api/v2/products

Query String Versioning

Enable query string versioning.

builder.Services.AddApiVersioning(options =>
{
    options.ApiVersionReader =
        new QueryStringApiVersionReader(
            "api-version");
});

Example request:

GET /api/products?api-version=2.0

This approach keeps the URL unchanged while allowing clients to specify a version.

Header Versioning

Read the version from a request header.

builder.Services.AddApiVersioning(options =>
{
    options.ApiVersionReader =
        new HeaderApiVersionReader(
            "api-version");
});

Example:

GET /api/products

api-version: 2.0

Header versioning is common in enterprise environments where clean URLs are preferred.

Deprecating an API Version

Mark an older version as deprecated.

[ApiVersion("1.0", Deprecated = true)]

Clients receive version information through response headers when ReportApiVersions is enabled, helping them plan migrations.

Version-Specific Endpoints

Some endpoints may exist only in newer versions.

Version 1:

[HttpGet]
public IActionResult GetProducts()
{
    return Ok();
}

Version 2:

[HttpGet]
public IActionResult GetProducts()
{
    return Ok(new
    {
        Version = "2",
        SupportsFiltering = true
    });
}

New functionality can be introduced without breaking existing clients.

API Documentation

If you're using Swagger/OpenAPI, generate separate documentation for each API version.

Example structure:

/swagger/v1/swagger.json

/swagger/v2/swagger.json

Separate documentation makes it easier for consumers to understand the capabilities of each version.

End-to-End Request Flow

A versioned request typically follows these steps:

  1. Client sends a request.

  2. ASP.NET Core determines the requested API version.

  3. The routing system selects the matching controller or action.

  4. Business logic executes.

  5. A version-specific response is returned.

  6. Response headers indicate supported and deprecated versions (if configured).

This process enables multiple API versions to coexist within the same application.

Versioning Strategy Comparison

StrategyReadabilityClient FriendlyREST FriendlyRecommended
URL PathExcellentExcellentYesYes
Query StringGoodGoodModerateYes
HeaderModerateGoodYesEnterprise APIs
Media TypeAdvancedModerateYesSpecialized APIs

Migration Strategy

A successful API evolution process typically includes:

Introduce a New Version

Create a new API version without modifying existing endpoints.

Support Both Versions

Allow clients time to migrate by keeping previous versions operational.

Mark Older Versions as Deprecated

Communicate deprecation through documentation and response headers.

Remove Deprecated Versions

After the announced support period ends, retire obsolete versions in a controlled manner.

Best Practices

  • Define an API versioning strategy before the first production release.

  • Avoid breaking changes within the same API version.

  • Deprecate versions before removing them.

  • Maintain separate API documentation for each version.

  • Keep response contracts consistent within a version.

  • Version only when changes are breaking.

  • Communicate version lifecycle clearly to API consumers.

  • Monitor usage of older API versions before retirement.

Common Mistakes

MistakeImpact
Changing response contracts without versioningBroken clients
Supporting too many versions indefinitelyHigher maintenance cost
Removing old versions without noticeService disruption
Mixing versioning strategies inconsistentlyConfusing API design
Failing to document differencesPoor developer experience
Introducing breaking changes in minor updatesCompatibility issues

Troubleshooting

Incorrect Controller Is Selected

Verify:

  • Route template

  • API version attributes

  • Version reader configuration

  • Endpoint registration

Clients Receive Unsupported Version Errors

Review:

  • Requested version

  • Supported versions

  • Default version configuration

  • API documentation

Swagger Shows Only One Version

Ensure separate Swagger documents are generated and mapped for each API version.

FAQs

Which versioning strategy is most common?

URL path versioning is widely used because it is simple, explicit, and easy to understand.

When should I create a new API version?

Create a new version whenever you introduce breaking changes to request or response contracts.

Should every new feature require a new version?

No. Backward-compatible additions can usually be introduced within the existing version.

How long should old API versions be supported?

The support period depends on your organization's API lifecycle policy and client migration requirements.

Can multiple API versions run in the same application?

Yes. ASP.NET Core supports hosting multiple API versions simultaneously, allowing gradual client migration.

Conclusion

API versioning is essential for maintaining backward compatibility while allowing APIs to evolve over time. A well-planned versioning strategy minimizes disruptions, improves the developer experience, and enables clients to adopt new features at their own pace.

By selecting an appropriate versioning approach, documenting changes clearly, and managing deprecation carefully, you can build ASP.NET Core APIs that remain stable, maintainable, and production-ready as they grow.