Advanced versioning in ASP.NET Core Web API

Different approaches in ASP.NET Core Web API versioning

Some time ago I wrote an article about ASP.NET Core Web API versioning which describes route based Web API endpoint versioning. I love route versioning and to me it is the best way of versioning because it is pretty straight, but there are situations where this kind of versioning is not suitable.

On nuget.org you can find Microsoft.AspNetCore.Mvc.Versioning package which gives you more options on how you can version your Web API endpoints. Nice thing with this package is that allows you using attributes with parameters directly on the controller, so it is pretty convenient for using.

Another good thing is that you can choose to report to the client that version it is trying to invoke is not supported. Just enable this option when you are adding versionign service in Startup.cs class

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddApiVersioning(o => o.ReportApiVersions = true);
        }
    

If you just decided to start versioning your API and you did not have versionign so far you can set the default versioning as well. All requests which have no versioning data

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddApiVersioning(o =>
            {
                o.ReportApiVersions = true;
                o.AssumeDefaultVersionWhenUnspecified = true;
                o.DefaultApiVersion = new ApiVersion(1, 0);
            });
        }
    
Note

Make sure that your default version is not marked as Deprecated on the controller and your client calls do not get rejected by versioning policy declared by versioning attributes on the controller

Let's start with some code examples and settings for different versioning options to see how all of this looks like in practice.

Parameter based versioning

Query string versioning is supported out of the box, so by sending api-version=1.0 we are telling out application to route to controller with version attribute 1.0

namespace Core.Versioning.Sample.Controllers
{

    [ApiVersion("1.0")]
    [Route("api/[controller]")]
    public class ValueController : Controller
    {
        [HttpGet]
        public String Get()
        {
            return "Version 1.0";
        }
    }
}
    

To add a new version we can add the same controller name inside the different namespace with ApiVersion attribute to a new version

namespace Core.Versioning.Sample.Controllers.v2
{

    [ApiVersion("2.0")]
    [ApiVersion("1.0", Deprecated = true)]
    [Route("api/[controller]")]
    public class ValueController : Controller
    {
        [HttpGet]
        public String Get()
        {
            return "Version 2.0";
        }
    }
}
    

Now let' test the response for different api-version parameter values

$ curl http://localhost:5000/api/value?api-version=1.0 -i

HTTP/1.1 200 OK
Date: Thu, 14 Jun 2018 08:11:13 GMT
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
api-supported-versions: 1.0, 2.0

Version 1.0

And for version 2.0 the response will be different

$ curl http://localhost:5000/api/value?api-version=2.0 -i

HTTP/1.1 200 OK
Date: Thu, 14 Jun 2018 08:11:19 GMT
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
api-supported-versions: 1.0, 2.0

Now I will try to call the API with version 3.0 which is not supported. Let's see what happens in that case

$ curl http://localhost:5000/api/value?api-version=3.0 -i

HTTP/1.1 400 Bad Request
Date: Thu, 14 Jun 2018 08:16:12 GMT
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
api-supported-versions: 1.0, 2.0

{  
   "error":{  
      "code":"UnsupportedApiVersion",
      "message":"The HTTP resource that matches the request URI 'http://localhost:5000/api/value?api-version=3.0' does not support the API version '3.0'.",
      "innerError":null
   }
}

You see that response code is not 200 OK, but 400 Bad Request with JSON message that this version is not supported.

Header based versioning

If sending version to your Web API endpoint as a query string value is not good enough you can use headers instead. For this way of versioning you need to do some small changes in your Startup.cs file to tell versioning package to look for version value in the headers for the specific key. I choose to use the same as I used for the query string which is api-version

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddApiVersioning(o =>
            {
                o.ReportApiVersions = true;
                o.AssumeDefaultVersionWhenUnspecified = true;
                o.DefaultApiVersion = new ApiVersion(1, 0);
                o.ApiVersionReader = new HeaderApiVersionReader("api-version");
            });
        }
    

You can easily test this with POSTMAN by adding api-version value in the request headers. With this approach, you will always use same endpoint url with a different request header values to access different version of your endpoints.

For a quick test just to confirm this is working, let's call the API with version 2.0 in headers

$ curl --header "api-version: 2.0" http://localhost:5000/api/value -i

HTTP/1.1 200 OK
Date: Thu, 14 Jun 2018 08:27:49 GMT
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
api-supported-versions: 1.0, 2.0

Version 2.0

Route based versioning

Route based versioning is based on the url structure of your controller and expects a route to contain version. For this purpose we need to change our Startup.cs to no longer read from the headers, bu to look in the url

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddApiVersioning(o =>
            {
                o.ReportApiVersions = true;
                o.AssumeDefaultVersionWhenUnspecified = true;
                o.DefaultApiVersion = new ApiVersion(1, 0);
                o.ApiVersionReader = new UrlSegmentApiVersionReader();
            });
        }
    

To support the default version which we set to be 1.0 we also need to have an additional route for the specific version without a version segment, otherwise default version vill not work

namespace Core.Versioning.Sample.Controllers
{

    [ApiVersion("1.0")]
    [ApiVersion("1.0", Deprecated = true)]
    [Route("api/[controller]")]
    [Route("api/v{version:apiVersion}/[controller]")]
    public class ValueController : Controller
    {
        [HttpGet]
        public String Get()
        {
            return "Version 1.0";
        }
    }
}

namespace Core.Versioning.Sample.Controllers.v2
{

    [ApiVersion("2.0")]
    [Route("api/v{version:apiVersion}/[controller]")]
    public class ValueController : Controller
    {
        [HttpGet]
        public String Get()
        {
            return "Version 2.0";
        }
    }
}
    

Now let's check the response with curl

$ curl http://localhost:5000/api/value -i

HTTP/1.1 200 OK
Date: Thu, 14 Jun 2018 08:03:34 GMT
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
api-supported-versions: 1.0, 2.0
api-deprecated-versions: 1.0

Version 1.0

You might notice that response contains api-deprecated-vesion header key set to 1.0. This is because our new controller has the following attribute

ApiVersion("1.0", Deprecated = true)
    

This tells Web API client that version 1.0 is not supported anymore. This can be pretty useful if your REST service is supporting multiple diffrent clients from different vendors.

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article