Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Version Interleaving

API versions do not have to be split across different controller classes. A service author might choose to have a controller implement multiple API versions simultaneously. Controller actions can subsequently be mapped to specific API versions. This approach is useful for small version differences but should be used sparingly to prevent developer confusion and complicate code maintenance. For example:

Minimal API

var builder = WebApplication.CreateBuilder( args );

builder.Services.AddProblemDetails();
builder.Services.AddApiVersioning();

var app = builder.Build();
var hello = app.NewVersionedApi();
var v1 = hello.MapGroup( "/helloworld" ).HasApiVersion( 1.0 );
var v2_v3 = hello.MapGroup( "/helloworld" )
                 .HasApiVersion( 2.0 )
                 .HasApiVersion( 3.0 );

v1.MapGet( "/", () => "Hello world v1.0!" );
v2_v3.MapGet( "/", () => "Hello world v2.0!" ).MapToApiVersion( 2.0 );
v2_v3.MapGet( "/", () => "Hello world v3.0!" ).MapToApiVersion( 3.0 );

app.Run();

MVC (Core)

[ApiVersion( 1.0 )]
[ApiController]
[Route( "api/[controller]" )]
public class HelloWorldController : ControllerBase
{
    [HttpGet]
    public string Get() => "Hello world v1.0!";
}

[ApiVersion( 2.0 )]
[ApiVersion( 3.0 )]
[ApiController]
[Route( "api/helloworld" )]
public class HelloWorld2Controller : ControllerBase
{
    [HttpGet]
    public string Get() => "Hello world v2.0!";

    [HttpGet, MapToApiVersion( 3.0 )]
    public string GetV3() => "Hello world v3.0!";
}

OData

[ApiVersion( 1.0 )]
public class PeopleController : ODataController
{
    public IActionResult Get( ODataQueryOptions<Person> options ) =>
        Ok( new[]{ new Person() } );
}

[ApiVersion( 2.0 )]
[ApiVersion( 3.0 )]
[ControllerName( "People" )]
public class People2Controller : ODataController
{
    public IActionResult Get( ODataQueryOptions<Person> options ) =>
        Ok( new[]{ new Person() } );

    [MapToApiVersion( 3.0 )]
    public IActionResult GetV3( ODataQueryOptions<Person> options ) =>
        Ok( new[]{ new Person() } );
}

Although not illustrated in these examples, it’s important to note that different versions of a service action might have different return values. The effect of the API versioning attribution is that the following requests match different controller and action implementations:

Request URLMatched ControllerMatched Action
/api/helloworld?api-version=1.0HelloWorldControllerGet
/api/helloworld?api-version=2.0HelloWorld2ControllerGet
/api/helloworld?api-version=3.0HelloWorld2ControllerGetV3
/api/People?api-version=1.0PeopleControllerGet
/api/People?api-version=2.0People2ControllerGet
/api/People?api-version=3.0People2ControllerGetV3

It should be reiterated that the defined API version, even for an action, never directly influences routing. When the action matched for a route is ambiguous, the selection process will look for an explicit API version that matches the requested API version. If an explicit match is not found, then the action will be implicitly matched. If two actions are ambiguous by route and API version, then this is a developer mistake and the default behavior is unchanged.