joydip_kanjilal
Contributor

How to version your Web API

how-to
Mar 24, 20164 mins
APIsC#Microsoft .NET

Take advantage of the various ways to version your Web API to make it more flexible and adaptable to changes while keeping the functionality intact

CSO > global binary connections
Credit: Matejmo / Getty Images

You should always version your Web API while at the same time keeping as much of the same URI as possible. Imagine a situation where you have a Web API that is up and running in production and is being consumed by users. Now suppose you need more functionality in the Web API yet must keep the existing functionality intact. You may have a few users who still need the old API, while others will need a version with new or extended features. This is exactly where Web API versioning comes to the rescue.

You can version your Web API in one of the following ways:

  1. Use URLs: Version information is specified in the URL as a query string.
  2. Use Custom Request Headers: Version information for your controller is specified in the request header sans the need for any changes in the URL.
  3. Use Accept Headers: Accept headers generally define the media type and character encodings. You can pass version information for your Web API via accept headers without having to change the URL.

Versioning Web API using URLs

Consider the following Web API controllers, which have been named AuthorsV1Controller and AuthorsV2Controller respectively.

public class AuthorsV1Controller : ApiController
    {
        [HttpGet]
        public IEnumerable<string> GetAuthors()
        {
          return new string[] { "Joydip Kanjilal", "Gerben Wierda" };
        }
    }
public class AuthorsV2Controller : ApiController
    {
        [HttpGet]
        public IEnumerable<string> GetAuthors()
        {
            return new string[] { "Joydip Kanjilal, INDIA", "Gerben Wierda, Netherlands" };
        }
    }

To simplify this illustration, I’ve incorporated a method named GetAuthors() in each controller. While GetAuthors() in AuthorsV1Controller returns only the author names, GetAuthors() in AuthorsV2Controller (the new version) returns the author names along with the names of the countries in which the authors reside.

The following code snippet shows how the two controllers use the Register method of the WebApiConfig class.

config.Routes.MapHttpRoute(
                name: "IDGWebAPIV1",
                routeTemplate: "api/v1/{controller}/{id}",
                defaults: new { controller= "AuthorsV1Controller", action="GetAuthors", id = RouteParameter.Optional }
            );
config.Routes.MapHttpRoute(
                name: "IDGWebAPIV2",
                routeTemplate: "api/v2/{controller}/{id}",
                defaults: new { controller = "AuthorsV2Controller", action = "GetAuthors", id = RouteParameter.Optional }
            );

You can now invoke the Web API method GetAuthors using the following URL.

http://localhost/IDGWebAPI/api/v1/Authors/GetAuthors

Versioning Web API using the request header

You can also implement Web API versioning using the request header. To achieve this, you need to implement a custom class that extends the DefaultHttpControllerSelector class, then override SelectController in your custom class. Note that the DefaultHttpControllerSelector class implements the IHttpControllerSelector interface. SelectController calls GetControllerName internally and accepts an instance of HttpRequestMessage as a parameter.

The following code snippet illustrates how you can retrieve version information from the request header.

private string GetControllerVersionFromRequestHeader(HttpRequestMessage request)
        {
            var acceptHeader = request.Headers.Accept;
            const string headerName = "Version";
            string controllerVersion = string.Empty;
            if (request.Headers.Contains(headerName))
            {
                controllerVersion = "V"+request.Headers.GetValues(headerName).First();               
            }
            return controllerVersion;
        }

Versioning Web API using the accept header

The following method shows how you can retrieve version information for your Web API from the accept header. The method checks the MIME type and returns the version information appropriately. If the media type is not application/json, the default version is returned as V1.

private string GetControllerVersionFromAcceptHeader(HttpRequestMessage request)
        {
            var acceptHeader = request.Headers.Accept;
            string controllerVersion = string.Empty;
            foreach (var mime in acceptHeader)
            {
                if (mime.MediaType.Equals("application/json"))
                {
                    NameValueHeaderValue version = mime.Parameters.FirstOrDefault(v => v.Name.Equals("Version", StringComparison.OrdinalIgnoreCase));
                    controllerVersion = "V" + version.Value.ToString();
                    return controllerVersion;
                }
            }
            return "V1";
        }

You can invoke your Web API from Fiddler by passing the accept header as shown below.

Accept: application/json; charset=utf-8;version=2

The following code listing illustrates how you can override SelectController to select a controller dynamically. Note how GetControllerVersionFromRequestHeader has been used. If you would like to retrieve the controller version from the accept header, you should leverage GetControllerVersionFromAcceptHeader instead.

public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            try
            {
                string controllerName = base.GetControllerName(request);
                var controllers = GetControllerMapping();
                var routeData = request.GetRouteData();
                string controllerVersion = GetControllerVersionFromRequestHeader(request);             
                controllerName = String.Format("{0}{1}", controllerName, controllerVersion);
                HttpControllerDescriptor controllerDescriptor;
                if (!controllers.TryGetValue(controllerName, out controllerDescriptor))
                {
                    string message = "No HTTP resource was found that matches the specified request URI {0}";
                    throw new HttpResponseException(request.CreateErrorResponse(System.Net.HttpStatusCode.NotFound, String.Format(message, request.RequestUri)));
                }
                return controllerDescriptor;
            }
            catch (Exception ex)
            {
                throw new HttpResponseException(request.CreateErrorResponse(System.Net.HttpStatusCode.NotFound, String.Format(ex.Message, request.RequestUri)));
            }
        }

You should add the following line in the Register method of the WebApiConfig class to provide support for controller selection at runtime.

config.Services.Replace(typeof(IHttpControllerSelector), new IDGControllerSelector((config)));

You can now use Fiddler to test your Web API — use the composer tab of Fiddler and provide the URL and version information as appropriate. If you want version 2 of your Web API controller to be invoked, you should specify Version: 2 when composing the request header information in the Composer tab in Fiddler.

joydip_kanjilal
Contributor

Joydip Kanjilal is a Microsoft Most Valuable Professional (MVP) in ASP.NET, as well as a speaker and the author of several books and articles. He received the prestigious MVP award for 2007, 2008, 2009, 2010, 2011, and 2012.

He has more than 20 years of experience in IT, with more than 16 years in Microsoft .Net and related technologies. He has been selected as MSDN Featured Developer of the Fortnight (MSDN) and as Community Credit Winner several times.

He is the author of eight books and more than 500 articles. Many of his articles have been featured at Microsoft’s Official Site on ASP.Net.

He was a speaker at the Spark IT 2010 event and at the Dr. Dobb’s Conference 2014 in Bangalore. He has also worked as a judge for the Jolt Awards at Dr. Dobb's Journal. He is a regular speaker at the SSWUG Virtual Conference, which is held twice each year.

More from this author