API Versioning is either something you love or you hate. It’s great for giving developers the ability to improve and iterate on API’s without breaking contracts. At times the stagnation of innovation on an API is simply because of legacy decisions that cannot be reversed, especially on public API’s. But versioning can quickly get out of control, especially with custom implementations.
Microsoft has attempted to alleviate some of the pain with it’s own versioning package which can be used in ASP.net core (And other .net platforms). It can be a bit tricky to get going and it takes a few “aha” moments to get everything sitting right. So let’s see how it works.
Setup
First, you will need to install the following nuget package from your package manager console.
Install-Package Microsoft.AspNetCore.Mvc.Versioning
In the ConfigureServices method of your startup.cs, you need to add the API Versioning services like so :
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddApiVersioning(o => { o.ReportApiVersions = true; o.AssumeDefaultVersionWhenUnspecified = true; o.DefaultApiVersion = new ApiVersion(1, 0); }); }
The ReportAPIVersions flag is optional, but it can be useful. It allows for the API to return versions in a response header. When calling an API with this flag on, you will see something like the following.
The flag for “AssumeDefaultVersionWhenUnspecified” (Quite the mouthful), can also be handy especially when migrating an API to versioning. Without it, you will break any existing clients that aren’t specifying an API version at all. If you are getting the error “An API version is required, but was not specified.”, this will be why.
The DefaultApiVersion flag is also not needed in this case because it defaults to 1.0. But I thought it helpful to include it as a reminder that you can “auto update” any clients that aren’t specifying a default API version to the latest. There is pros and cons to doing this, but the option is there if you want it.
URL Query Based Versioning
Take a look at the following basic controllers. They have been decorated with an ApiVersion attribute, and their classes have been updated so they don’t clash with each other.
[ApiVersion("1.0")] [Route("api/home")] public class HomeV1Controller : Controller { [HttpGet] public string Get() => "Version 1"; } [ApiVersion("2.0")] [Route("api/home")] public class HomeV2Controller : Controller { [HttpGet] public string Get() => "Version 2"; }
You’ll notice that the routes are actually the same, so how does ASP.net core determine which class to use?
Actually the default way that the versioning library uses is by using a query string of “api-version” to specify a version.
So when I call /api/home?api-version=2.0, I am returned “Version 2”. When I call /api/home?api-version=1.0 (Or no version at all), I am returned “Version 1”.
URL Path Based Versioning
Query string parameters are nice and easy but don’t always look the best (And they can be a pain for an external client to always tag on). In a big long query they can be missed in the sea of query parameters. A common alternative is to put the version in the URL path so it’s always visible at the start of the URL.
Take the following two controllers :
[ApiVersion("1.0")] [Route("api/{version:apiVersion}/home")] public class HomeV1Controller : Controller { [HttpGet] public string Get() => "Version 1"; } [ApiVersion("2.0")] [Route("api/{version:apiVersion}/home")] public class HomeV2Controller : Controller { [HttpGet] public string Get() => "Version 2"; }
Now when I call /api/1.0/home, I am returned Version 1. And /api/2.0/home will return me version 2.
Http Header Based Versioning
Specifying a version via an Http Header is a very common way of using Api Versioning. It allows your urls to stay clean without cluttering them with version information.
The defaults in the aspnet versioning package don’t actually support header information. You need to do a bit more work.
In your ConfigureServices method in startup.cs, you need to add the option of an ApiVersionReader to your AddApiVersioning call. Like so :
services.AddApiVersioning(o => { o.ApiVersionReader = new HeaderApiVersionReader("x-api-version"); });
With this call I have told my API that the header “x-api-version” is now how I define an API version.
One word of warning. The above makes it so that you cannot use query string versioning anymore. So once you set the version reader to use the header, you can no longer specify the version like so /api/home?api-version=2.0
If you wish to use both, you need to use the aptly named “QueryStringOrHeaderApiVersionReader” which frankly is a ridiculous name but I guess it does what it says on the tin.
services.AddApiVersioning(o => { o.ApiVersionReader = new QueryStringOrHeaderApiVersionReader("x-api-version"); });
Version A Single Action
There may come a time when you want to only create a new version of an action, but not the entire controller (Infact there will be plenty of times). There is a way to do this, but a word of warning is that it will mean at some point in the future, you will have a mismatch of controllers, actions and versions etc. It can be hard to manage.
But to version a single action, you can do something that looks like the following :
[ApiVersion("1.0")] [ApiVersion("2.0")] [Route("api/home")] public class HomeController : Controller { [HttpGet] public string Get() => "Version 1"; [HttpGet, MapToApiVersion("2.0")] public string GetV2() => "Version 2"; }
Essentially we still need to tell the Controller that it supports 2.0, but within the controller we can use the “MapToApiVersion” attribute to tell it to be used with a specific version.
Deprecating A Version
You are able to deprecate an Api version. Note that this does not “delete” the version, it only marks it as deprecated so a consumer can know. When someone calls your API, they will see the following header returned.
But the point is, they can still call that endpoint/version. It does not limit it in any way.
Conventions Based Setup
Up to now we have defined the API Versioning with an attribute. This is fine but it can get out of hand when you have many controllers with different versions with no way to have an “overview” of the versions you have in play. It’s also a little limiting in terms of configuration.
Enter “Conventions” setup. It’s an easy way to define your versioning when adding the APIVersioning services. Take a look at the following configuration :
services.AddApiVersioning(o => { o.ReportApiVersions = true; o.AssumeDefaultVersionWhenUnspecified = true; o.DefaultApiVersion = new ApiVersion(1, 0); o.Conventions.Controller<HomeV1Controller>().HasApiVersion(new ApiVersion(1, 0)); o.Conventions.Controller<HomeV2Controller>().HasApiVersion(new ApiVersion(2, 0)); });
Pretty self explanatory and now we don’t have to put attributes on our controllers anymore and the configuration is all stored in the same location.
Accessing HTTP Version
There may come a time when you actually want to know what API Version was requested. While I would recommend not going down the route of large switch statements to find services (Or passing them into factories etc), there may be an occasion when you have a legitimate use for it.
Luckily, HttpContext has a method called “GetRequestedApiVersion” that will return you all the version info.
[Route("api/home")] public class HomeV1Controller : Controller { [HttpGet] public string Get() => HttpContext.GetRequestedApiVersion().ToString(); }
Opting Out Of Versioning
A scenario that is becoming more and more common is having a single project that works as an API and an MVC app all in one. When you add versioning to your API, your MVC app suddenly becomes versioned also. But there is a way to “opt out” if you will.
Take the following example :
[ApiVersionNeutral] [Route("api/optout")] public class OptOutControler : Controller { [HttpGet] public string Get() => HttpContext.GetRequestedApiVersion().ToString(); }
Now actually, if you pass in a version of 2.0, this action will actually return 2.0. It still knows the version and can read it, it simply doesn’t care. Thus if you “force” a version number or minimum version, this controller is unaffected.
Anything I missed?
Have I missed something? Feel free to comment below.
For more information click here
in release 1.1 you need add app.UseApiVersioning() in the Configure method.
Thanks.
This is awesome… thanks for writing this!… Everything I want to know in a single blog post.
Good article, but when I am applying with .net core 2.0 and web api it is not working.
I have two controllers values and values2 controller with same route name api/values with different apiversion 1.0 and 2.0. I am applying apiversioning by header but I am getting this error.
“error”:{“code”:”UnsupportedApiVersion”,”message”:”The HTTP resource that matches the request URI ‘http://localhost:57789/api/values’ is not supported.”,”innerError”:null}}
Hi there,
Could you make a minimal solution and throw it up on Github so I can take a look for you?
What if I don’t use
[ApiVersion(“1.0”)]
[Route(“api/{version:apiVersion}/home”)]
and simply modify the route as [Route(“api/v1/home”)] and [Route(“api/v2/home”)] on my controllers. Is there a technical difference or advantage between the two (apart from, of course, yours being the correct way of doing)
Thanks for the tip! What is the best practice for organizing a service with multiple APIs which has different speed of evolution? Should I:
1. Create one controller for each API, do not tag the controller with version and just version the API methods?
2. Create one controller with multiple APIs and follow the same practice as #1
3. Create one controller with multiple APIs, for each major version change a new controller is created.
4. Others…
bottom line.. I can’t see why we are versioning the controller, and I’m trying to give different APIs their own version so they all can evolve at different speed. Can you please advise? Thanks!
Good point. You probably wouldn’t have different endpoints evolving at different speeds. However it’s typically for if you have two “different” API’s so for example you work for a financial institution and you have your regular API and a “Crypto” API, but these live inside the same project, these can obviously evolve at different speeds.
Is there any support for a “patch” in the versioning instead of just “major.minor” versions? (i.e. 1.0.0, 1.1.0 etc)
How can I restrict a user to call version no 3 and 5?
I have 7 versions and now I dont want any Qa form my team access v3 and v5 . I am passing version from postman. I am getting the error ” version not supported….” if i pass any version beside 1…..7. Now i want to show the same error if anybody pass “x-api-version” : 3 or 5.