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); }); }
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
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
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
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
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
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.
Comments for this article