For the last 4 weeks, I was working on building a REST API for LeadX. LeadX is a mobile-first CRM and considering is a product we are building from scratch we were able to use current set of best practices and also apply our previous learnings from the DeltaX platform. We used .NET Core for building the API and as part of this post I plan to discuss how we implemented API versioning - along with what it is and why it’s matters?

The problem with API is - it evolves, and evolving an API is very tricky. We can’t just change the response of the API or change the parameters it accepts overnight. API is an agreement between the client and our platform. So the key question is how will we evolve our platform without breaking existing agreement with our clients.Here comes API Versioning - we can version our API’s so that client code will not break with our new changes and when they successfully migrated to our new API versions we can easily able to deprecated the old versions.

ASP.NET Core provides built-in support for API Versioning by simply adding AddApiVersioning() middleware in Startup.cs. It also provides extension methods to configure the middleware accordingly.


public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    services.AddApiVersioning();
}

NOTE - Install Microsoft.AspNetCore.Mvc.Versioning package from NuGet

  • Add ApiVersion Attribute to the Controller to specify which versions controller really supports.

[ApiController]
[Route("api/info")]
[ApiVersion("1.0")]
[ApiVersion("1.1")]
public class InfoController : ControllerBase
{
}
  • Add MapToApiVersion Attribute to ActionMethod to specify for what version it will map to.

[HttpGet]
[MapToApiVersion("1.0")]
public ActionResult<IEnumerable<InfoModel>> Get()
{
}

URL Versioning

Part of Query String

It’s the default way of passing Version info to application

https://localhost:44349/api/info?api-version=1.0

api-version=1.0 tells the controller that we are looking for a resources with 1.0 version.

Query Parameter

Part of URI

We can also provide Version info in URI instead of passing a query like -

https://localhost:44349/api/v1.0/info

and in the controller update Route Attribute

[Route("api/v{version:apiVersion}/info")]

Part of Header

Another common way is to pass as Header for that we need to configure our middleware to use HeaderApiVersionReader extension method.


services.AddApiVersioning(config  =>
{
    // using Microsoft.AspNetCore.Mvc.Versioning;
    config.ApiVersionReader = new HeaderApiVersionReader("api-version");
});

Version Header

There are others way to versioning which we’ll not discuss here like Content-Type, etc.

If we don’t specify version info in any way then we’ll get an error message ApiVersionUnspecified from the application.

ApiVersionUnspecified

Or you can use DefaultApiVersion extension method to specify default API version if the client does not provide explicit version.


services.AddApiVersioning(config =>
{
    config.AssumeDefaultVersionWhenUnspecified = true;
    config.DefaultApiVersion = new ApiVersion(1, 0);
    config.ReportApiVersions = true;
});

ReportApiVersions = true indicates version compatablity infomation in response, so our client can know which all version we are supporting for a resource.

ReportApiVersion

API Deprecation

By moving forward with new Versions, we also need to deprecate some of our old API Versions. To do that we can simply add Deprecated = true in [ApiVersion]Attribute like [ApiVersion("1.0", Deprecated = true)] this will add an extra header in response to inform client which Versions are been deprecated for a resource, but still accept requests for that (deprecated) Version.

Deprecated

API Conventions

Every time we add a new version we need to specify it in Controller and Methods


[Route("api/info")]
[ApiVersion("1.0")]
[ApiVersion("1.1")]
[ApiVersion("1.2")]
[ApiVersion("1.3")]
[ApiVersion("2.0")]
[ApiVersion("2.1")]
[ApiController]
public class InfoController : ControllerBase
{
    [HttpGet]
    [MapToApiVersion("1.0")]
    public ActionResult Get10()
    {
    }

    [HttpGet]
    [MapToApiVersion("1.1")]
    public ActionResult Get12)
    {
    }
    ....
    ....
    [HttpGet]
    [MapToApiVersion("2.1")]
    public ActionResult Get21()
    {
    }
}

Or we can make our Controller look cleaner using Conventions


services.AddApiVersioning(config =>
{
    config.AssumeDefaultVersionWhenUnspecified = true;
    config.DefaultApiVersion = new ApiVersion(1, 0);
    config.ReportApiVersions = true;

    config.ApiVersionReader = new HeaderApiVersionReader("api-version");

    config.Conventions.Controller<InfoController>()
        .HasApiVersion(new ApiVersion(1, 0))
        .HasApiVersion(new ApiVersion(1, 1))
        .HasApiVersion(new ApiVersion(1, 2))
        .HasApiVersion(new ApiVersion(1, 3))
        .HasApiVersion(new ApiVersion(2, 0))
        .HasApiVersion(new ApiVersion(2, 1))
        .Action(typeof(ConventionController).GetMethod("Get10"))
        .MapToApiVersion(new ApiVersion(1, 0))
        .Action(typeof(ConventionController).GetMethod("Get11"))
        .MapToApiVersion(new ApiVersion(1, 1))
        ....
        ....
        .Action(typeof(ConventionController).GetMethod("Get22"))
        .MapToApiVersion(new ApiVersion(2, 2));
});

Our Controller looks a lot cleaner.


[Route("api/info")]
[ApiController]
public class InfoController : ControllerBase
{
    [HttpGet]
    public ActionResult Get10()
    {
    }
    ....
    ....
    [HttpGet]
    public ActionResult Get22()
    {
    }
  
}

At Last, What if we don’t want to version our some Controllers like About, Contact etc… for that, we can use [ApiVersionNeutral] Attribute in the Controller


[ApiVersionNeutral]
[Route("api/neutral")]
[ApiController]
public class NeutralController : ControllerBase
{
    [HttpGet]
    public ActionResult Get()
    {
        ....
        ....
    }
}

and now our Controller is Version Neutral with no api-supported-versions header in the response.

Version Neutral

Using API Versioning, we have an API’s which we can easily evolve without breaking client integrations.

Resources

  • Code for this demo application is available at GitHub
  • Official Microsoft Repository on GitHub contain sample application related to API Versioning
  • Microsoft REST API Guidelines