How to Implement API Versioning in .NET 10 with Microsoft.AspNetCore.OpenApi and Asp.Versioning

This article focuses on API versioning in .NET 10. It uses Microsoft.AspNetCore.OpenApi together with Asp.Versioning to connect versioned routing, document grouping, and visual API documentation, solving the compatibility risks that often appear as APIs evolve. Keywords: .NET 10, OpenAPI, API Versioning.

Technical Specifications Snapshot

Parameter Description
Language C# / ASP.NET Core
Runtime .NET 10
Versioning Strategy URL Path Versioning
OpenAPI Library Microsoft.AspNetCore.OpenApi 10.0.0
Versioning Libraries Asp.Versioning.Http / Mvc / ApiExplorer 10.0.0
Documentation UI Scalar.AspNetCore 2.6.0
Protocol HTTP / OpenAPI
Repository denglei1024/openapi-apiversion
Star Count Not provided in the original article
Core Dependencies Asp.Versioning, Microsoft.AspNetCore.OpenApi, Scalar

API Versioning Is the Foundation for Safe API Evolution

Once clients integrate with an API, that API stops being just code implementation and becomes a stable contract. Without versioning, adding fields, changing response structures, or retiring old endpoints can directly break production consumers.

In Web API design, versioning mainly solves three problems: backward compatibility, incremental evolution, and controlled deprecation. It allows teams to keep shipping new capabilities without disrupting existing clients.

Common Versioning Strategies Should Match Team Boundaries

Common approaches include URL path versioning, query string versioning, header versioning, and media type versioning. Among them, URL versioning is the most intuitive and the easiest to standardize across gateways, logs, and documentation systems.

/api/v1/users
/api/users?api-version=1.0
X-API-Version: 1.0
Accept: application/json; v=1.0

This example shows the four mainstream ways to declare an API version. For the ASP.NET Core scenario in this article, the URL path strategy is the best fit.

.NET 10 Unifies Native OpenAPI and Version Governance More Naturally

In the past, many .NET projects relied on Swashbuckle for documentation generation and layered Asp.Versioning on top for version handling. That combination works, but the configuration chain is longer and the integration between versions and documentation is less natural.

In .NET 10, Microsoft provides Microsoft.AspNetCore.OpenApi. At the same time, Asp.Versioning v10 can now integrate directly with this library. That means version grouping, document generation, and route constraints can now work together within a single capability set.

Start by Installing the Dependencies Required for Multi-Version Documentation

# API versioning
 dotnet add package Asp.Versioning.Http --version 10.0.0
 dotnet add package Asp.Versioning.Mvc --version 10.0.0
 dotnet add package Asp.Versioning.Mvc.ApiExplorer --version 10.0.0

# OpenAPI document generation
 dotnet add package Microsoft.AspNetCore.OpenApi --version 10.0.0

# Documentation UI
 dotnet add package Scalar.AspNetCore --version 2.6.0

These dependencies handle version detection, MVC integration, document grouping, and visual documentation rendering respectively.

Program.cs Must Configure Both Version Declaration and Document Grouping

The core idea has two steps: first, teach the application how to read API versions; second, generate an independent OpenAPI document for each version. This way, consumers see clearly separated v1 and v2 documents instead of one mixed collection.

var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;

services
    .AddApiVersioning(options =>
    {
        options.DefaultApiVersion = new ApiVersion(1, 0); // Set the default version to 1.0
        options.AssumeDefaultVersionWhenUnspecified = true; // Use the default when no version is specified
        options.ReportApiVersions = true; // Report supported versions in response headers
        options.ApiVersionReader = new UrlSegmentApiVersionReader(); // Read the version number from the URL path
    })
    .AddMvc()
    .AddApiExplorer(options =>
    {
        options.GroupNameFormat = "'v'V"; // Generate group names such as v1 and v2
        options.SubstituteApiVersionInUrl = true; // Replace the route version placeholder with the actual value automatically
    });

services.AddOpenApi("v1", options =>
{
    options.ShouldInclude = api => api.GroupName == "v1"; // Include only v1 endpoints
});

services.AddOpenApi("v2", options =>
{
    options.ShouldInclude = api => api.GroupName == "v2"; // Include only v2 endpoints
});

var app = builder.Build();

app.MapOpenApi();
app.MapScalarApiReference(options =>
{
    options.WithTitle("Users API - {documentName}")
           .AddDocuments(new[] { "v1", "v2" }); // Display multiple versioned documents in the UI
});

app.Run();

This configuration delivers three key capabilities: version-aware routing, grouped OpenAPI output, and multi-document rendering in Scalar.

Controller Attributes Must Explicitly Declare API Versions

Configuring the base services is not enough. Controllers and action methods must also tell the framework which API version they belong to. Only then can the framework route requests correctly and place endpoints into the correct document.

[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
public class UsersController : ControllerBase
{
    [HttpGet]
    [MapToApiVersion("1.0")]
    public IActionResult GetV1()
    {
        return Ok(new
        {
            Version = "v1", // Return the current API version
            Users = new[] { "Alice", "Bob" }
        });
    }
}

[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("2.0")]
public class UsersV2Controller : ControllerBase
{
    [HttpGet]
    [MapToApiVersion("2.0")]
    public IActionResult GetV2()
    {
        return Ok(new
        {
            Version = "v2", // Return the upgraded API version
            Users = new[] { "Alice", "Bob", "Charlie" }
        });
    }
}

This code demonstrates how to bind different controllers to different versions through attributes and return version-specific data structures.

Access Paths and Output Behavior Clearly Reflect Version Isolation

After versioned routing is enabled, clients can access different implementations through different URLs, which prevents new logic from overwriting old behavior.

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

This shows that the API version is part of the routing contract. Clients get a clear upgrade path, and the server can evolve more safely in stages.

Scalar Turns Multi-Version OpenAPI Documents Into a Readable Developer Portal

The original article also introduces Scalar as the API reference presentation layer. It does not manage versions by itself, but it is well suited for browsing, switching, and searching across multiple documents.

API documentation interface

AI Visual Insight: This image shows a multi-column API documentation layout, typically including endpoint navigation on the left, request details and examples in the center, and an environment or version switcher at the top. This structure helps developers quickly locate endpoints across multiple API versions, inspect parameters and response models, and verify that document grouping is configured correctly.

If your team plans to expose OpenAPI output directly to frontend developers, QA engineers, or third-party integrators, a documentation frontend like Scalar can significantly improve API discoverability and collaboration efficiency.

This Approach Works Especially Well for ASP.NET Core Services That Need Long-Term Compatibility

The value of this setup is not just that it can generate documentation. It unifies versioning strategy, route constraints, grouped documents, and the reading experience into one engineering workflow.

For medium and large systems, you should define a version lifecycle policy early: default version, deprecation notices, migration windows, and document freeze rules. Tools enforce the mechanics, but governance rules determine long-term maintainability.

Reference Repository

FAQ

1. Why not just keep using Swashbuckle?

If your existing project already runs stably, Swashbuckle is still a valid choice. But for new .NET 10 projects, Microsoft.AspNetCore.OpenApi integrates more naturally with the framework, and the configuration path becomes more consistent when combined with Asp.Versioning.

2. Is URL versioning better than header-based versioning?

There is no universally better option. URL versioning is more intuitive and works well for gateway forwarding, log analysis, and documentation display. Header-based versioning is more semantically pure, but it is slightly weaker in debugging visibility and operational transparency. Teams should choose based on consumers and infrastructure.

3. Can one controller support multiple API versions at the same time?

Yes. You can declare multiple ApiVersion attributes on the same controller, then use MapToApiVersion to map different actions to different versions. However, if the differences between versions are substantial, splitting controllers is usually easier to maintain.

[AI Readability Summary]

This article reconstructs a complete API versioning solution for .NET 10 using Microsoft.AspNetCore.OpenApi and Asp.Versioning. It covers dependency installation, Program.cs configuration, controller version annotations, multi-version OpenAPI document generation, and Scalar-based presentation. It is especially suitable for ASP.NET Core developers who need to balance backward compatibility with documentation governance.