When you make any changes in the API, consumer should be able to continue using API in the way they were using before the changes were made. This is where the versioning is essential. This is a way in which the API evolves to add new features to it without breaking the current way of consuming it. In my view, this is not the same as new assembly version or new build. Whatever you change behind which the consumer cannot see in the API, should not require the new version of the API. The new version of the API should only be rolled out whenever “how to consume part changes”. Ex: when the resource or the signature changes.
If the API is a bit complex and serves for wide range consumers, then API versioning is a must. There exist different approaches. I will touch upon all of them with respect to ASP.NET Web API versioning approaches and will suggest the best approach. I will also attempt to convince the reader with the appropriate examples and justification.
Approach #1: URI Versioning
We will add the version number to the URI, then that URI maps to method on that controller.
Version 1:
HTTP GET
URI
: /API/Report/1
Accept
: application/json
Version 2:
HTTP GET
URI
: /API/v2/Report/1
Accept
: application/json
Quite easy to implement. We can do it simply using the routing attribute over the method.
With the above approach we are breaking one of the constraints. The URI represents the resource and as long as the resource doesn’t change, the URI shouldn’t change as well. Of course, shouldn’t the URI to the same resource stay same? If the new version of the resource itself changes, then the new URI should be the valid approach. You might also want to make it accessible in a completely different URI. In that context, a new version of the resource is a different resource.
This approach is often used. One of the advantages with this is the browser exportability. We can access the different versions of the web API without having to resolve with applications like Fiddler to send custom requests to the web API. So we can categorize this one as a pragmatic approach.
Approach #2: Content Negotiation
In the previous example, we get the message as JSON or XML by setting the content type as application/json or application/xml. Through Custom Content Type in the Accept Header, we can create custom content types which will tell the web API about which version to return in the accept header.
Example:
Version 1:
HTTP GET
URI: /API/Report/1
Accept: application/json
Version 2:
HTTP GET
URI: /API/Report/1
Accept: application/vnd.servicename.v2+json
Here you are not only mentioning that you need data in JSON format but also information about the version. To create such API, vendor prefix
“vnd”has to be used. It indicates that the content type is vendor specific and followed by a custom identifier, the version and the resource format.
Ex: vnd.{customidentifier}.{version}.{content format}
In this approach, its a bit harder to browse the API in the browser. To invoke the API method, the request has to be built with the appropriate accept header.
Approach #3: Custom Request Header
The third approach is the custom header added to request containing the version number. It’s a kind of
Approach #2 but without the custom content type.
Here the API should read the custom header and from that info execute the correct version of the code.
Example:
Version 1:
HTTP GET
URI: /API/Report/1
Accept: application/json
Version 2:
HTTP GET
URI: /API/Report/1
Accept: application/json
API-version: 2
It’s a bit harder to browse and the request has to be created with the custom request header.
The approaches described above can be used in combination as well. Like, major versions are requested through the URI and the minor versions are requested through the request header.
Approach #4: The URI Parameter Versioning
This is an obvious one that I don’t see many people using:
Example : /customer/accounts?version=2
This method is highly used by Amazon, Google and Netflix, but still it is not so popular. The client knows about what version he wants and the web API provider has no problem to maintain several versions. When no version is specified, the client will get the latest or default version. Newer versions will not break existing hyperlinks as we are using the URL parameter and will not change the resource name and location.
On the server-side, this feels a bit harder – the servers have to parse the entire parameter string before knowing where to route the request – something they try to avoid. Also, there is the same argument as not putting version numbers into the URI – the parameters are for specifying the services function not attributes of the implementation.
ASP.NET Web API Implementation Examples:
Approach #1:
[Route(“Report/v1/{id}”)]
public IHttpActionResult Get(int id)
{
}
[Route(“ReportCollection/{reportCollectionId}/report/{id}”)]
[Route(“Report/v2/{id}”)]
public IHttpActionResult Get(int id, int? reportCollectionId)
{
}
As simple as this, we just change the version in the routing attribute. So I would like to pay more attention to other two approaches
Custom content Type and
Custom Request Header.
Approach #2:
When a consumer requests a resource representation to a certain URI, we have to read out the custom content type request. We can able to read in API method using Request Headers and we execute the correct code depending on what we find in the header, but doing this inside the API method is not the best way because the request ends up in the same controller and method and then we have to process it.
This approach is better implemented through Custom Route Factory attributes and custom routing constraint. In the custom constraint, we will be able to check the accept header or the custom request header as we are going to implement both types of versioning. The Custom Route Factory attributes will then use that constraint and each constraint will have an opportunity to prevent the route from the given request.
We are going to create a custom route factory attribute, versioned route that adds a constraint to each attribute route and then potentially prevents the route. This will allow us to decorate the controller or a specific method in the controller with that attribute.
If the request matches our constraint logic, it then allows the correct route else prevents the route. To implement in a generic way I have a modified version of the custom class “Version Constraint”.
VersionConstraint Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Web.Http.Routing;
namespace Report.API.Helpers
{
/// summary
/// A Constraint implementation that matches an HTTP header against an expected version value.
/// Matches both custom request headers (“API-version”) and custom content type vnd.myservice.vX+json(or other data type)
/// summary
internal class VersionConstraint : IHttpRouteConstraint
{
public const string VersionHeaderName = “API-version”;
private const int DefaultVersion = 1;
public VersionConstraint(int allowedVersion)
{
AllowedVersion = allowedVersion;
}
public int AllowedVersion
{
get;
private set;
}
public bool Match(HttpRequestMessage request, IHttpRoute route,
string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
if (routeDirection == HttpRouteDirection.URIResolution)
{
// try custom request header “API-version”
int? version = GetVersionFromCustomRequestHeader(request);
// not found? Try custom content type in accept header
if (version == null)
{
version = GetVersionFromCustomContentType(request);
}
return ((version ?? DefaultVersion) == AllowedVersion);
}
return true;
}
private int? GetVersionFromCustomContentType(HttpRequestMessage request)
{
string versionAsString = null;
// get the accept header.
<code class="csharp keyword">var</code> <code class="csharp plain">mediaTypes = request.Headers.Accept.Select(h => h.MediaType);</code>
string matchingMediaType = null;
// find the one with the version number – match through regex
Regex regEx = new Regex(@”application\/vnd\.ReportAPI\.v([\d]+)\+json”);
foreach (var mediaType in mediaTypes)
{
if (regEx.IsMatch(mediaType))
{
matchingMediaType = mediaType;
break;
}
}
if (matchingMediaType == null)
return null;
// extract the version number
Match m = regEx.Match(matchingMediaType);
versionAsString = m.Groups[1].Value;
// … and return
int version;
if (versionAsString != null && Int32.TryParse(versionAsString, out version))
{
return version;
}
return null;
}
private int? GetVersionFromCustomRequestHeader(HttpRequestMessage request)
{
string versionAsString;
IEnumerable<string> headerValues;
if (request.Headers.TryGetValues(VersionHeaderName, out headerValues) && headerValues.Count() == 1)
{
versionAsString = headerValues.First();
}
else
{
return null;
}
int version;
if (versionAsString != null && Int32.TryParse(versionAsString, out version))
{
return version;
}
return null;
}
}
}
Versioned Attribute:
using System.Collections.Generic;
using System.Web.Http.Routing;
namespace Report.API.Helpers
{
internal class VersionedRoute : RouteFactoryAttribute
{
public VersionedRoute(string template, int allowedVersion)
: base(template)
{
AllowedVersion = allowedVersion;
}
public int AllowedVersion
{
get;
private set;
}
public override IDictionary<string, object> Constraints
{
get
{
var constraints = new HttpRouteValueDictionary();
constraints.Add(“version”, new VersionConstraint(AllowedVersion));
return constraints;
}
}
}
}
Web API method after the implementation of the about classes.
Example for Approach #2 custom content type in accept header:
Sample GET version 1 method for Versioning with Custom content type in header:
(version 1 invoke example is similar for Approach #2 and Approach #3)
[VersionedRoute(“API/ReportCollection/{reportCollectionId}/report/{id}”), 1]
[VersionedRoute(“API/report/{id}”), 1]
public IHttpActionResult Getv1(int id, int? reportCollectionId)
{
//body
}
Execution : request with API/report/1 URI version #1 will be invoked
now requests with version #1 will match the above route. As we have mentioned version as #1 and its a default version, if we don’t pass the version in the header the default version #1 is executed.
Sample GET version 2 method for Versioning with Custom content type in accept header:
[VersionedRoute(“API/ReportCollection/{reportCollectionId}/report/{id}”),2]
[VersionedRoute(“API/report/{id}”),2]
public IHttpActionResult Getv2(int id, int? reportCollectionId)
{
//body
}
Execution: now to invoke version #2, in custom request header specify,
use same get call URI: API/report/1
Accept: application/vnd.ServiceAPIname.v2+json (sepecify in accept header not in URI)
now it will invoke Version #2 get method.
Sample GET version 1 method for Versioning with Custom request header:
[VersionedRoute(“API/ReportCollection/{reportCollectionId}/report/{id}”),1]
[VersionedRoute(“API/report/{id}”),1]
public IHttpActionResult Getv1(int id, int? reportCollectionId)
{
//body
}
Execution : request with API/report/1 URI version #1 will be invoked
now requests with version #1 will match the above route. As we have mentioned version as #1 and its a default version, if we don’t pass the version in the header the default version #1 is executed.
Sample GET version 2 method for Versioning with Custom request header:
[VersionedRoute(“API/ReportCollection/{reportCollectionId}/report/{id}”),2]
[VersionedRoute(“API/report/{id}”),2]
public IHttpActionResult Getv2(int id, int? reportCollectionId)
{
//body
}
Execution: now to invoke version #2, in custom request header specify,
use same get call URI: API/report/1
API-version: 2 (specify in request header not in URI)
now it will invoke Version #2 method.
Facebook and Twitter are using a part of the URL that comes before the API Service definition and before the resource definition itself (for example www.example.com/v2.7/{sampleparameter}) because one may say that the whole URL is the resource identifier.
However some may say that the resource identifier is only the {sampleparameter} and what comes before that is the HOST and the API SERVICE identifier. It is a bit more clear when using twitter API: https://api.twitter.com/1.1/blocksabc, here the HOST = api.twitter.com, API Service = blocks and the API Service version which comes before the API Service definition 1.1.
Conclusion
All versioning schemes are problematic if there is no necessity. Versioning is unnecessary if the clients are very few. For example, teams with just ONE client insist on a versioning scheme which is ridiculous. Resource type should not be changed in a way that its breaks backward compatibility. I have seen in many projects using “V1” in the resource URL such as “v1/GetData” and it never changes to “V2”. In such scenarios versioning is unnecessary.
I prefer
Approach #3 – custom header for a strong reason. I will justify why I don’t like others. URL relationships are great for versioning when it involves changes to resources and behaviors (let’s say you move from “user” to “customer” which now does a back end billing and email process but doesn’t change the structure of the resource itself). But what about if you need to change something on a different layer, such as going from OAuth 1.0 to OAuth 2.0? Or you’re doing a pivot as a company? Or something else I haven’t even thought of yet? I don’t like the URL approaches because they simply do something about the resource representation where as a header is, again, on a much higher layer. I hope to never change our header versioning, but having it there as a required parameter ensures we’ll never break integration who fail to specify what they want. Facebook and Twitter did that and it was not received well by the developer community. The very first request to our API makes this required parameter clear in an error response and once it’s handled, it’s never thought about again. As you said, proxies today handle it just fine. I’ve also read that we can drop the “X-” convention as we’re not thinking our custom header will ever be on the road to being a globally accepted.
Use a custom HTTP Header especially when versioning is required at an API Service level and not on a Resource level.
I
recommend implementing the
Approach #3 Versioning through custom request header, we don’t break the resource and feature maintenance principles which occurs in URI versioning. In this way, we can integrate the versioning built in with web API without interrupting the consumers who wants to access old versions. Consumer never faces the complexity of versioning in the URI instead they specify it in request header but more over if they fail to specify the version at least the default version will send the response. Thus, avoids the URI conflicts of unreachable routes.
Library for API versioning support in Microsoft ASP.NET Web API is available as a NuGet package. To install ASP.NET Web API versioning support, run the following command in the Package Manager Console
GitHub : https://github.com/Sebazzz/SDammann.WebApi.Versioning
PM > Install-Package SDammann.WebApi.Versioning -Version 2.8.0
Disadvantage:
Higher versioned GET methods cannot be accessed via browser. Instead it requires a web debugging tool like Fiddler to construct the request header with appropriate API-version number.
This approach can be quite problematic when you are using API Gateways. When you send the “api-version” header, it can cause some misleading to which API exactly you are referring to – the API Service that is your API Gateway (that can be versioned by it’s own) or the web API Service that should get the call from the API Gateway.