Skip to content

Existing Services Quick Start

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

While it's great to plan for an API versioning story for your services upfront, it's all too common to need API versioning after your services are in production. The ASP.NET versioning libraries provide features to help you retrofit existing services and integrate formal API versioning without breaking your existing clients.

Unless a service is API version-neutral, existing services have some logical, yet undefined, API version that is not formally declared by the service or known to a client. In order to prevent existing clients from breaking, they must be able to make requests to the original URL without specifying any API version information.

When API versioning is applied, all of the existing services now have an explicit API version on the service side. The initial, default API version is 1.0, but that can be configured to be a different API version. All existing controller definitions that do not have explicit API version definitions will now be implicitly bound to the default API version. Once a controller has any API version attribution or conventions, it will never be implicitly matched. This enables service authors to permanently sunset API versions over time. Controllers that have an implicit API version can be confusing to service authors; especially, in a team environment. It is recommended that you explicitly apply API versions to all of your existing services when you introduce formal API versioning.

ASP.NET Web API

public static class WebApiConfig
{
    public static void Configuration( HttpConfiguration configuration )
    {
        // allow a client to call you without specifying an api version
        // since we haven't configured it otherwise, the assumed api version will be 1.0
        configuration.AddApiVersioning( options => options.AssumeDefaultVersionWhenUnspecified = true );

        // remaining configuration omitted for brevity
    }
}

[ApiVersion( 1.0 )] // ← this attribute isn't required, but it's easier to understand
[RoutePrefix( "People" )]
public class PeopleController : ApiController
{
    // GET ~/people
    // GET ~/people?api-version=1.0
    [Route]
    public IHttpActionResult Get() => Ok( new[] { new Person() } );
}

[ApiVersion( 2.0 )]
[RoutePrefix( "People" )]
public class People2Controller : ApiController
{
    // GET ~/people?api-version=2.0
    [Route]
    public IHttpActionResult Get() => Ok( new[] { new Person() } );
}

ASP.NET Web API with OData

public static class WebApiConfig
{
    public static void Configuration( HttpConfiguration configuration )
    {
        // allow a client to call you without specifying an api version
        // since we haven't configured it otherwise, the assumed api version will be 1.0
        configuration.AddApiVersioning( options => options.AssumeDefaultVersionWhenUnspecified = true );

        var modelBuilder = new VersionedODataModelBuilder( configuration )
        {
            DefaultModelConfiguration = ( builder, apiVersion, routePrefix ) =>
            {
                builder.EntitySet<Person>( "People" );
            }
        };

        configuration.MapVersionedODataRoutes( "odata", null, modelBuilder );

        // remaining configuration omitted for brevity
    }
}

[ApiVersion( 1.0 )] // ← this attribute isn't required, but it's easier to understand
[ODataRoutePrefix( "People" )]
public class PeopleController : ODataController
{
    // GET ~/people
    // GET ~/people?api-version=1.0
    [EnableQuery]
    [ODataRoute]
    public IHttpActionResult Get() => Ok( new[] { new Person() } );
}

[ApiVersion( 2.0 )]
[ControllerName( "People" )]
[ODataRoutePrefix( "People" )]
public class People2Controller : ODataController
{
    // GET ~/people?api-version=2.0
    [EnableQuery]
    [ODataRoute]
    public IHttpActionResult Get() => Ok( new[] { new Person() } );
}

ASP.NET Core with Minimal APIs

var builder = WebApplication.CreateBuilder( args );

builder.Services.AddProblemDetails();

// allow a client to call you without specifying an api version
// since we haven't configured it otherwise, the assumed api version will be 1.0
builder.Services.AddApiVersioning( options => options.AssumeDefaultVersionWhenUnspecified = true );
        
var app = builder.Build();
var people = app.NewVersionedApi();
var v1 = people.MapGroup( "/people" ).HasApiVersion( 1.0 );
var v2 = people.MapGroup( "/people" ).HasApiVersion( 2.0 );

v1.MapGet( "/", () => new[] { new Person() } );
v2.MapGet( "/", () => new[] { new Person() } );

app.Run();

ASP.NET Core with MVC (Core)

[ApiVersion( 1.0 )] // ← this attribute isn't required, but it's easier to understand
[ApiController]
[Route( "[controller]" )]
public class PeopleController : ControllerBase
{
    [HttpGet]
    public IActionResult Get() => Ok( new[] { new Person() } );
}

[ApiVersion( 2.0 )]
[ApiController]
[Route( "People" )]
public class People2Controller : ControllerBase
{
    [HttpGet]
    public IActionResult Get() => Ok( new[] { new Person() } );
}
var builder = WebApplication.CreateBuilder( args );

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

// allow a client to call you without specifying an api version
// since we haven't configured it otherwise, the assumed api version will be 1.0
builder.Services.AddApiVersioning( options => options.AssumeDefaultVersionWhenUnspecified = true )
                .AddMvc();
        
var app = builder.Build();

app.MapController();
app.Run();

ASP.NET Core with OData

[ApiVersion( 1.0 )] // ← this attribute isn't required, but it's easier to understand
public class PeopleController : ODataController
{
    // GET ~/people
    // GET ~/people?api-version=1.0
    [EnableQuery]
    public IActionResult Get() => Ok( new[] { new Person() } );
}

[ApiVersion( 2.0 )]
[ControllerName( "People" )]
public class People2Controller : ODataController
{
    // GET ~/people?api-version=2.0
    [EnableQuery]
    public IActionResult Get() => Ok( new[] { new Person() } );
}
var builder = WebApplication.CreateBuilder( args );

builder.Services.AddControllers().AddOData();
builder.Services.AddProblemDetails();

// allow a client to call you without specifying an api version
// since we haven't configured it otherwise, the assumed api version will be 1.0
builder.Services
       .AddApiVersioning( options => options.AssumeDefaultVersionWhenUnspecified = true )
       .AddOData( options =>
        {
            options.ModelBuilder.DefaultModelConfiguration = ( builder, apiVersion, routePrefix ) =>
            {
                builder.EntitySet<Person>( "People" );
            };
            options.AddRouteComponents();
        } );

var app = builder.Build();

app.MapController();
app.Run();
Clone this wiki locally