Skip to content

Version Interleaving

Chris Martinez edited this page Dec 29, 2022 · 6 revisions

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:

ASP.NET Web API

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

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

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

ASP.NET Web API and OData

[ApiVersion( 1.0 )]
[ODataRoutePrefix( "People" )]
public class PeopleController : ODataController
{
    [ODataRoute]
    public IHttpActionResult Get( ODataQueryOptions<Person> options ) =>
        Ok( new[]{ new Person() } );
}

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

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

ASP.NET Core with 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!";
}

ASP.NET Core and 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() } );
}

ASP.NET Core with Minimal APIs

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();

Although not illustrated in this example, 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 URL Matched Controller Matched Action
/api/helloworld?api-version=1.0 HelloWorldController Get
/api/helloworld?api-version=2.0 HelloWorld2Controller Get
/api/helloworld?api-version=3.0 HelloWorld2Controller GetV3
/api/People?api-version=1.0 PeopleController Get
/api/People?api-version=2.0 People2Controller Get
/api/People?api-version=3.0 People2Controller GetV3

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.

Clone this wiki locally