Click here to Skip to main content
Click here to Skip to main content

Introduction to Web API Versioning

, 10 Mar 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
ASP.NET Web API Versioning based on namespace and custom HTTP headers.

Introduction

In this article lets discuss about Web API versioning and the need for the same.

Versioning of API is very much required if you are providing services for the public. It's the only way we can make our API's supporting forward and backward compatibility. By versioning you don't effect the existing customers and break their applications which depends on your API.

The default Web API routing logic finds the controller by class name. The controller selection is handled internally by Web API through DefaultHttpControllerSelector which implements IHttpControllerSelector.SelectController. We can't really version Web API's with this default controller selector but we can plug in our custom implementation for selecting the right version of our controller at run time. By implementing IHttpControllerSelector interface one can easily create a custom HTTP controller selector.

Let us see two different kinds of Web API versioning. You can decide on which ever is best and suits your needs. One can either go with namespace or HTTP custom header based versioning mechanism.

Background

Knowledgeable and have good understanding and development experience in Web API.


Some more interesting articles on Web API

1. Introduction to ASP.NET Web API
http://www.codeproject.com/Articles/549152/Introduction-to-ASP-NET-Web-API

2. ASP.NET WebAPI Hosting Techniques

http://www.codeproject.com/Articles/555923/ASP-NET-WebAPI-Hosting-Techniques

3. Implementing Custom DelegatingHandler in ASP.NET WebAPI

http://www.codeproject.com/Articles/557232/Implementing-Custom-DelegatingHandler-in-ASP-NET-W

4. Implementing Custom Media Formatters in ASP.NET WebAPI

http://www.codeproject.com/Articles/559378/Implementing-Custom-Media-Formatters-in-ASP-NET-We

Using the code

Below is the code snippet for getting the version number from HTTP custom header. Let us assume the default version of our Web API will always be empty.

private string GetVersionFromHTTPHeader(HttpRequestMessage request)
{
      if (request.Headers.Contains("version"))
      {
          var versionHeader = request.Headers.GetValues("version").FirstOrDefault();
          if (versionHeader != null)
          {
               return versionHeader;
          }
      }

      return string.Empty;
}

Below is the code snippet to get the version number based on the Mime Type. You can use either of the code to get the version number.

 private string GetVersionFromAcceptHeaderVersion(HttpRequestMessage request)
 {
       var acceptHeader = request.Headers.Accept;

       foreach (var mime in acceptHeader)
       {
             if (mime.MediaType == "application/json" || mime.MediaType == "text/html")
             {
                  var version = mime.Parameters
                                   .Where(v => v.Name.Equals("version", StringComparison.OrdinalIgnoreCase))
                                    .FirstOrDefault();

                   if (version != null)
                   {
                       return version.Value;
                   }
                   return string.Empty;
             }
       }
       return string.Empty;
 } 

Below is the code snippet for custom Web API controller selector which implements IHttpControllerSelector.SelectController. From the below code you can notice that we are returning the controller descriptor based on the matched controller mappings or by version.

public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
        var controllers = GetControllerMapping(); 

        var routeData = request.GetRouteData();

        var controllerName = routeData.Values["controller"].ToString();

        HttpControllerDescriptor controllerDescriptor;

        if (controllers.TryGetValue(controllerName, out controllerDescriptor))
        {
                var version = GetVersionFromHTTPHeader(request);

                if (!string.IsNullOrEmpty(version))
                {
                    var versionedControllerName = string.Concat(controllerName, "V", version);

                    HttpControllerDescriptor versionedControllerDescriptor;
                    if (controllers.TryGetValue(versionedControllerName, out versionedControllerDescriptor))
                    {
                        return versionedControllerDescriptor;
                    }
                }

                return controllerDescriptor;
        }

        return null;
} 

So what next ?

Now that we have implemented custom controller selector. We will have to just replace the default one and make use of our custom WebAPI controller selector. This can be easily done by adding one line of code in WebApiConfig.

public static class WebApiConfig
{
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            // Replace the controller configuration selector
            config.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector((config)));
        }
}


Below is the snapshot of the HTTP Request and Response. You can notice the "version" is being passed in HTTP Request header.






We shall see in brief about namespace based Web API versioning. This would be one of the most common technique one would make use and you will see most of the popular service providers do make use of these kind of techniques.

Below is the code snippet which essentially acts as a look up by returning the controller descriptor based on the namespace.

private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
{
            var dictionary = new Dictionary<string,>(StringComparer.OrdinalIgnoreCase);

            // Create a lookup table where key is "namespace.controller". The value of "namespace" is the last
            // segment of the full namespace. For example:
            // MyApplication.Controllers.V1.ProductsController => "V1.Products"
            IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
            IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();

            ICollection<type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);

            foreach (Type t in controllerTypes)
            {
                var segments = t.Namespace.Split(Type.Delimiter);

                // For the dictionary key, strip "Controller" from the end of the type name.
                // This matches the behavior of DefaultHttpControllerSelector.
                var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);

                var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", segments[segments.Length - 1], controllerName);

                // Check for duplicate keys.
                if (dictionary.Keys.Contains(key))
                {
                    _duplicates.Add(key);
                }
                else
                {
                    dictionary[key] = new HttpControllerDescriptor(_configuration, t.Name, t);  
                }
            }

            // Remove any duplicates from the dictionary, because these create ambiguous matches. 
            // For example, "Foo.V1.ProductsController" and "Bar.V1.ProductsController" both map to "v1.products".
            foreach (string s in _duplicates)
            {
                dictionary.Remove(s);
            }
            return dictionary;
}

Below is the code snippet for namespace based custom controller selector.

We will be getting the namespace and controller from the HTTP Request Message and then we will be finding the matching controller with in the dictionary. In order to find the match we will build the matching key as <namespace name>:<controller name>

public HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
            IHttpRouteData routeData = request.GetRouteData();
            if (routeData == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            // Get the namespace and controller variables from the route data.
            string namespaceName = GetRouteVariable<string>(routeData, “namespace”);
            if (namespaceName == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            string controllerName = GetRouteVariable<string>(routeData, “controller”);
            if (controllerName == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            // Find a matching controller.
            string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", namespaceName, controllerName);

            HttpControllerDescriptor controllerDescriptor;
            if (_controllers.Value.TryGetValue(key, out controllerDescriptor))
            {
                return controllerDescriptor;
            }
            else if (_duplicates.Contains(key))
            {
                throw new HttpResponseException(
                    request.CreateErrorResponse(HttpStatusCode.InternalServerError,
                    "Multiple controllers were found that match this request."));
            }
            else
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
}

// Get a value from the route data, if present.
private static T GetRouteVariable<t>(IHttpRouteData routeData, string name)
{
            object result = null;
            if (routeData.Values.TryGetValue(name, out result))
            {
                return (T)result;
            }
            return default(T);
}

Below is the code snippet on the usage of namespace based WebAPI Versioning. You can notice the HTTP Request has the version and controller names specified.

static void RunClient()
{
      HttpClient client = new HttpClient();
      client.BaseAddress = _baseAddress;

      using (HttpResponseMessage response = client.GetAsync("api/v1/values").Result)
      {
          response.EnsureSuccessStatusCode();
          string content = response.Content.ReadAsStringAsync().Result;
          Console.WriteLine("Version 1 response: '{0}'\n", content);
      }

      using (HttpResponseMessage response = client.GetAsync("api/v2/values").Result)
      {
          response.EnsureSuccessStatusCode();
          string content = response.Content.ReadAsStringAsync().Result;
          Console.WriteLine("Version 2 response: '{0}'\n", content);
      }
}

References

http://blogs.msdn.com/b/webdev/archive/2013/03/08/using-namespaces-to-version-web-apis.aspx

https://mathieu.fenniak.net/aint-nobody-got-time-for-that-api-versioning/

http://aspnet.codeplex.com/SourceControl/changeset/view/dd207952fa86#Samples/WebApi/NamespaceControllerSelector/

http://bitoftech.net/2013/12/16/asp-net-web-api-versioning-strategy/

Points of Interest

Versioning of Web API for me it's a required thing in what ever service that I can think of providing it to the public. It was really a fun in implementing Web API versioning. I really love the plug and play or replacement of HTTP Controller Selector.

History

Version 1.0 - Initial Release of Article - 03/09/2014

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ranjan.D
Web Developer
United States United States
Profile
 
Around 10 years of professional software development experience in analysis, design, development, testing and implementation of enterprise web applications for healthcare domain with good exposure to object-oriented design, software architectures, design patterns, test-driven development and agile practices.
 
In Brief
 
Analyse and create High Level , Detailed Design documents.
Use UML Modelling and create Use Cases , Class Diagram , Component Model , Deployment Diagram, Sequence Diagram in HLD.
 
Area of Working : Dedicated to Microsoft .NET Technologies
Experience with : C# , J2EE , J2ME, Windows Phone 8, Windows Store App
Proficient in: C# , XML , XHTML, XML, HTML5, Javascript, Jquery, CSS, SQL, LINQ, EF
 
Software Development
 
Database: Microsoft SQL Server, FoxPro
Development Frameworks: Microsoft .NET 1.1, 2.0, 3.5, 4.5
UI: Windows Forms, Windows Presentation Foundation, ASP.NET Web Forms and ASP.NET MVC3, MVC4
Coding: WinForm , Web Development, Windows Phone, WinRT Programming, WCF, WebAPI
 
Healthcare Domain Experience
 
CCD, CCR, QRDA, HIE, HL7 V3, Healthcare Interoperability
 
Others:
 
TTD, BDD
 
Education
 
B.E (Computer Science)
 
CodeProject Contest So Far:
 
1. Windows Azure Developer Contest - HealthReunion - A Windows Azure based healthcare product , link - http://www.codeproject.com/Articles/582535/HealthReunion-A-Windows-Azure-based-healthcare-pro
 
2. DnB Developer Contest - DNB Business Lookup and Analytics , link - http://www.codeproject.com/Articles/618344/DNB-Business-Lookup-and-Analytics
 
3. Intel Ultrabook Contest - Journey from development, code signing to publishing my App to Intel AppUp , link - http://www.codeproject.com/Articles/517482/Journey-from-development-code-signing-to-publishin
 
4. Intel App Innovation Contest 2013 - eHealthCare - http://www.codeproject.com/Articles/635815/eHealthCare
 
5. Grand Prize Winner of CodeProject HTML5 &CSS3 Article Contest 2014
 
6. Grand Prize Winner of CodeProject Android Article Contest 2014

Comments and Discussions

 
QuestionExcellent article Pinmembertasleem hussein4-Aug-14 12:52 
AnswerRe: Excellent article PinmvpRanjan.D4-Aug-14 14:50 
GeneralMy vote of 5 Pinmembermaq_rohit a.k.a asthanarht11-Apr-14 13:11 
GeneralRe: My vote of 5 PinmvpRanjan.D11-Apr-14 15:47 
SuggestionCache version-controller relationship Pinmemberkelton502018-Mar-14 7:34 
GeneralRe: Cache version-controller relationship PinmvpRanjan.D18-Mar-14 15:49 
QuestionMy Vote of 5 PinmemberAbhishek Nandy10-Mar-14 22:34 
AnswerRe: My Vote of 5 PinmvpRanjan.D11-Mar-14 15:12 
NewsMorning Brew PinmemberJérôme VIBERT10-Mar-14 1:21 
GeneralRe: Morning Brew PinmvpRanjan.D10-Mar-14 1:32 
GeneralMy vote of 5 PinmemberTaiseer Joudeh9-Mar-14 18:29 
GeneralRe: My vote of 5 PinmvpRanjan.D10-Mar-14 1:42 
GeneralRe: My vote of 5 PinmemberDewey12-Mar-14 21:38 
GeneralRe: My vote of 5 PinmvpRanjan.D13-Mar-14 1:35 
QuestionGood article on webappi versioning PinmemberSuman Rani9-Mar-14 18:29 
AnswerRe: Good article on webappi versioning PinmvpRanjan.D10-Mar-14 1:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141220.1 | Last Updated 10 Mar 2014
Article Copyright 2014 by Ranjan.D
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid