API Versioning in ASP.net Core

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.

In the ConfigureServices method of your startup.cs, you need to add the API Versioning services like so :

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.

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 :

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 :

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.

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 :

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 :

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.

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 :

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.

1 comment

Leave a Reply

Your email address will not be published. Required fields are marked *