This is the tenth part of Building ASP.Net Web API RESTful Service Series. The topics we’ll cover are:
- Building the Database Model using Entity Framework Code First – Part 1.
- Applying the Repository Pattern for the Data Access Layer – Part 2.
- Getting started with ASP.Net Web API – Part 3.
- Implement Model Factory, Dependency Injection and Configuring Formatters – Part 4.
- Implement HTTP actions POST, PUT, and DELETE In Web API – Part 5.
- Implement Resources Association – Part 6.
- Implement Resources Pagination – Part 7.
- Securing Web API – Part 8.
- Preparing Web API for Versioning – Part 9.
- Different techniques to Implement Versioning – Part 10 (This Post).
- Caching resources using CacheCow and ETag – Part 11.
Update (2014-March-5) Two new posts which cover ASP.Net Web API 2 new features:
Different techniques to Implement Versioning
In this post we’ll discuss four different techniques for API versioning, we’ll start with the most common one which is versioning using URI, then we’ll cover versioning using query string, custom header, and finally using accept header.
Web API Versioning using URI
Including the version number in the URI considers the most common way of versioning, so we can consume a resource by calling http://localhost:{your_port}/api/v1/students/ (client wants to use version 1 of the API) or http://localhost:{your_port}/api/v2/students/ (client wants to use version 2 of the API).
The advantage of this technique that clients know which version they are using. To implement this we need to add two new routes inside the Register method in “WebApiConfig” class as the code below:
1 2 3 4 5 6 7 8 9 10 11 |
config.Routes.MapHttpRoute( name: "Students", routeTemplate: "api/v1/students/{userName}", defaults: new { controller = "students", userName = RouteParameter.Optional } ); config.Routes.MapHttpRoute( name: "Students2", routeTemplate: "api/v2/students/{userName}", defaults: new { controller = "studentsV2", userName = RouteParameter.Optional } ); |
Notice in the code above how we added two new routes and mapped each route with it is corresponding controller. i.e. Route named “Students2” is by default mapped to controller “studentsV2”. In future if we want to add version “V3” then we need to add new route to select the appropriate controller, this might get messy over time.
The drawbacks for this technique that it is not compliance with REST specification because the URI will keep changing by time, as well we need to keep maintaining new routes once we introduce a new version.
Before we jump and discuss the implementation of the other three techniques we need to take a look on how Web API selects the appropriate controller based on the information sent with the request.
Web API uses a method named “SelectController” in class”DefaultHttpControllerSelector”, this method accept “HttpRequestMessage” object which contains a key/value pair of route data including controller name which is defined in class “WebApiConfig”. Based on this information and by doing reflection on all classes which derives from “ApiController” class, Web API can match the controller name with the appropriate class, if there is a duplicate or class is not found, then an exception will be thrown.
To override this default implementation we need to add new class named “LearningControllerSelector” which derives from class “Http.Dispatcher.DefaultHttpControllerSelector”, then we need to override the method “SelectController”, let’s take a look on the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
public class LearningControllerSelector : DefaultHttpControllerSelector { private HttpConfiguration _config; public LearningControllerSelector(HttpConfiguration config) : base(config) { _config = config; } public override HttpControllerDescriptor SelectController(HttpRequestMessage request) { var controllers = GetControllerMapping(); //Will ignore any controls in same name even if they are in different namepsace var routeData = request.GetRouteData(); var controllerName = routeData.Values["controller"].ToString(); HttpControllerDescriptor controllerDescriptor; if (controllers.TryGetValue(controllerName, out controllerDescriptor)) { var version = "2"; var versionedControllerName = string.Concat(controllerName, "V", version); HttpControllerDescriptor versionedControllerDescriptor; if (controllers.TryGetValue(versionedControllerName, out versionedControllerDescriptor)) { return versionedControllerDescriptor; } return controllerDescriptor; } return null; } } |
What we have done here is:
- Getting a dictionary of all classes (API Controllers) which derives from “ApiController” class by calling method “GetControllerMapping()”.
- Retrieving the route data from the request “request.GetRouteData()” then looking for the controller name in this request.
- Trying to get an object of type “HttpControllerDescriptor” based on the controller name we retrieved from the request. The HttpControllerDescriptor objects will contain information that describes the selected controller.
- If we found the controller, then we will try to find a versioned controller on the form “ControllerNameV2” using the same technique we used in the previous step. We will cover now how we will get the version number, but for now we can fix it to “2”.
Now we need to use this custom “Controller Selector” we’ve implemented instead of the default one, we can do this by adding the code below inside “Register” method in class “WebApiConfig”
1 |
config.Services.Replace(typeof(IHttpControllerSelector), new LearningControllerSelector((config))); |
Now we will implement versioning by sending the version number with the request object:
Web API Versioning using Query String
Versioning API by query string is straight forward, we will add query parameter such as “?v=2” to the URI, the request will be as: http://localhost:{your_port}/api/students/?v=2
We will assume that clients who didn’t provide the version in query string want the oldest version (v=1).
To implement this we’ll add method named “GetVersionFromQueryString” which accepts the “Request” object where we can read query string from it and decide which version client wants. Method will be added to class “LearningControllerSelector”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private string GetVersionFromQueryString(HttpRequestMessage request) { var query = HttpUtility.ParseQueryString(request.RequestUri.Query); var version = query["v"]; if (version != null) { return version; } return "1"; } |
We’ve to call this method inside method “SelectController” to read the version number and select the appropriate controller. The only drawback for this technique is that URI will keep changing by time and this is not compliance with REST specifications.
API Versioning by Custom Header
Now will try another approach where the version is set in the header request not as a part of the URI, we will add our custom header named “X-Learning-Version” and set the version there. We’ll assume that clients who didn’t provide the header value are requesting the oldest version of the API (Version 1), to implement this we need to add new function named “GetVersionFromHeader” to “LearningControllerSelector” class as the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
private string GetVersionFromHeader(HttpRequestMessage request) { const string HEADER_NAME = "X-Learning-Version"; if (request.Headers.Contains(HEADER_NAME)) { var versionHeader = request.Headers.GetValues(HEADER_NAME).FirstOrDefault(); if (versionHeader != null) { return versionHeader; } } return "1"; } |
Basically what we have done here is simple, we hard-coded the custom header name, and checked if the headers collection contains this name, if so we tried to get the value from this header.
To test this out we need to issue GET request using fiddler as the image below, note how we added the new header “X-Learning-Version” to request header collection:
The only drawback for this technique is that we are introducing new custom header to the headers collection, we can version by headers using the Accept Header without introducing new custom header as we’ll see in fourth technique below.
API Versioning using Accept Header
In this approach we’ll version by using the “Accept” header. Out GET request accept header will be on form: “Accept:application/json; version=2”.
We’ll assume that clients who didn’t provide the version value in accept header are requesting the oldest version of the API (Version 1), to implement this we need to add new function named “GetVersionFromAcceptHeaderVersion” to “LearningControllerSelector” class as the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
private string GetVersionFromAcceptHeaderVersion(HttpRequestMessage request) { var acceptHeader = request.Headers.Accept; foreach (var mime in acceptHeader) { if (mime.MediaType == "application/json") { var version = mime.Parameters .Where(v => v.Name.Equals("version", StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); if (version != null) { return version.Value; } return "1"; } } return "1"; } |
In the code above we are looping on all header values collection and examining if the media type is “application/json”, if this is the case then we get the value of the parameter named “version”.
To test this out we need to issue GET request using fiddler as the image below, note how we added version parameter to the “Accept” header:
This is more standard way of API versioning because we didn’t add new custom header, and we didn’t version the API by changing the URI.
If you have plural sight subscription; I highly recommend watching this course where Shawn Wildermuth covers another ways for API versioning.
In the next post we’ll talk about resources cashing using Entity Tags.
Reblogged this on Sutoprise Avenue, A SutoCom Source.
What about version via hypermedia?
Thank you for introducing four methods of doing versioning. You mention that only the accept-header-based complies with ‘the REST standard’. Wikipedia says that REST is not a standard, but rather an architectural style, so I am confused. Can you point out the standard that the other three techniques violate? IMHO, there is something to be said for putting the version in the URI or query string since it makes it obvious what version is being requested. In fact, I think the query string may be easiest since it does not require additional routing. Comments?
Hello Jeff and thanks for your feedback,
I didn’t say in this post that REST is a standard or has standards, I’ve said that versioning by URI don’t go along with “REST Specifications” and you are totally correct; REST is an architectural style that you can adhere to while building your http service.
There is no single “right” method to version your API, the only thing I don’t like about URI versioning that you will have multiple versions (different URIs) to serve the same resource, and this contradicts with REST specs (Uniform and Unique Resource Identifier).
As I mentioned in the post, personally I prefer versioning using Accept header so I can keep the URI the same even if I have N versions of the same resource.
Hope this answers your question.
the accept header approach looks the cleanest, but have you seen any approaches that allow for semver based versioning? (before I go off and roll my own)
As far as I see it, that would mean:
– parse the accept header for semver version (ie 1.0.0) and remove the dots, so our controllers are now SomeV100Controller and SomeV123Controller
– (nice to have) when passing an accet-header, allow wildcards, so 1.*.* will find the most recent minor version but avoid people inadvertantly picking up major version changes, and 1.2.* would only pick up patches for the more cautious folks out there. Otherwise, in a semver based system, people either have to omit the header, and risk a major (possibly breaking) change. Or include it but with more specificity than you’d probably want
Thanks for your posts. I believe there is a minor error in the logic/flow of the SelectController method. If the requested controller name is matched from the controller’s dictionary–typically a controller without versioning–then it should be returned. Only if there was no match, then we need to continue and attempt extracting the versioning from either query string or Accept header and try match again. Finally, returning null if no controller is matched despite appending the versioning info. Basically negating the conditional and switching between “return null” and “return controlerDescriptor”.