Building ASP.Net Web API RESTful Service – Part 10

This is the tenth part of Building ASP.Net Web API RESTful Service Series. The topics we’ll cover are:

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:

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:

What we have done here is:

  1. Getting a dictionary of all classes (API Controllers) which derives from “ApiController” class by calling method “GetControllerMapping()”.
  2. Retrieving the route data from the request “request.GetRouteData()” then looking for the controller name in this request.
  3. 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.
  4. 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”

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”

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:

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:

API Version by Custom Header

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:

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:

Web API Version by 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.

Source code is available on GitHub.

Comments

  1. Jeff Roughgarden says

    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?

    • says

      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.

Trackbacks

Leave a Reply