Versioning ASP.NET Web API Services Using HTTP Headers

Posted on September 25, 2012

11


I’ve been doing some work with APIs lately and finally had the chance to dig into the ASP.NET Web API a bit more. While it’s technically brand new (released with .NET 4.5 and Visual Studio 2012), the Web API has been around in beta form for quite a bit now. For those of us who have done a fair amount of work with the WCF framework, the Web API is a welcome addition/replacement. Instead of monstrous configuration files and contract-first demands placed on us by WCF, we can now build RESTful web services using a very lightweight and HTTP-focused framework. As I work on designing a new API, one thing that I’m focused on right now is versioning. In this blog post, I’ll show you how to build HTTP-header-based versioning for ASP.NET Web API services.

Service designers have a few choices when it comes to versioning their services. What seems like the default option for many is to simply replace the existing service with a new one and hope that no consumers get busted. However, that’s pretty rough and hopefully less frequent than it was in the early days of service design. In the must-read REST API Design Handbook (see my review),  author George Reese points out three main options:

  • HTTP Headers. Set the version number in a custom HTTP header for each request.
  • URI Component. This seems to be the most common one. Here, the version is part of the URI (e.g. /customerservice/v1/customers).
  • Query Parameter. In this case, a parameter is added to each incoming request (e.g. /customerservice/customers?version=1).

George (now) likes the first option, and I tend to agree. It’s nice to not force new URIs on the user each time a service changes. George finds that a version in the header fit nicely with other content negotiations that show up in HTTP headers (e.g. “content-type”). So, does the ASP.NET Web API support this natively? The answer is: pretty much. While you could try and choose different controller operations based on the inbound request, it’s even better to be able to select entirely different controllers based on the API version. Let’s see how that works.

First, in Visual Studio 2012, I created a new ASP.NET MVC4 project and chose the Web API template.

2012.09.25webapi01

Next, I wanted to add a new “model” that is the representation of my resource. In this example, my service works with an “Account” resource that has information about a particular service account owner.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.Serialization;

namespace Seroter.AspNetWebApi.VersionedSvc.Models
{
    [DataContract(Name = "Account", Namespace = "")]
    public class Account
    {
        [DataMember]
        public int Id { get; set; }
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public string TimeZone { get; set; }
        [DataMember]
        public string OwnerName { get; set; }
    }
}

Note that I don’t HAVE to use the “[DataContract]” and “[DataMember]” attributes, but I wanted a little more control over the outbound naming, so I decided to decorate my model this way. Next up, I created a new controller to respond to HTTP requests.

2012.09.25webapi02

The controller does a few things here. It loads up a static list of accounts, responds to “get all” and “get one” requests, and accepts new accounts via HTTP POST. The “GetAllAccounts” operation is named in a way that the Web API will automatically use that operation when the user requests all accounts (/api/accounts). The “GetAccount” operation responds to requests for a specific account via HTTP GET. Finally, the “PostAccount” operation is also named in a way that it is automatically wired up to any POST requests, and it returns the URI of the new resource in the response header.

public class AccountsController : ApiController
    {
        /// <summary>
        /// instantiate list of accounts
        /// </summary>
        Account[] accounts = new Account[]
        {
            new Account { Id = 100, Name = "Big Time Consulting", OwnerName = "Harry Simpson", TimeZone = "PST"},
            new Account { Id = 101, Name = "BTS Partners", OwnerName = "Bobby Thompson", TimeZone = "MST"},
            new Account { Id = 102, Name = "Westside Industries", OwnerName = "Ken Finley", TimeZone = "EST"},
            new Account { Id = 103, Name = "Cricket Toys", OwnerName = "Tim Headley", TimeZone = "PST"}
        };

        /// <summary>
        /// Returns all the accounts; happens automatically based on operation name
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Account> GetAllAccounts()
        {
            return accounts;
        }

        /// <summary>
        /// Returns a single account and uses an explicit [HttpGet] attribute
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet]
        public Account GetAccount(int id)
        {
            Account result = accounts.FirstOrDefault(acct => acct.Id == id);

            if (result == null)
            {
                HttpResponseMessage err = new HttpResponseMessage(HttpStatusCode.NotFound)
                {
                    ReasonPhrase = "No product found with that ID"
                };

                throw new HttpResponseException(err);
            }

            return result;
        }

        /// <summary>
        /// Creates a new account and returns HTTP code and URI of new resource representation
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        public HttpResponseMessage PostAccount(Account a)
        {
            Random r = new Random(1);

            a.Id = r.Next();
            var resp = Request.CreateResponse<Account>(HttpStatusCode.Created, a);

            //get URI of new resource and send it back in the header
            string uri = Url.Link("DefaultApi", new { id = a.Id });
            resp.Headers.Location = new Uri(uri);

            return resp;
        }
    }

At this point, I had a working service. Starting up the service and invoking it through Fiddler made it easy to interact with. For instance, a simple “get” targeted at http://localhost:6621/api/accounts returned the following JSON content:

2012.09.25webapi03

If I did an HTTP POST of some JSON to that same URI, I’d get back an HTTP 201 code and the location of my newly created resource.

2012.09.25webapi04

Neato. Now, something happened in our business and we need to change our API. Instead of just overwriting this one and breaking existing clients, we can easily add a new controller and leverage the very cool IHttpControllerSelector interface to select the right controller at runtime. First, I made a few updates to the Visual Studio project.

  • I added a new class (model) named AccountV2 which has additional data properties not found in the original model.
  • I changed the name of the original controller to AccountsControllerV1 and created a second controller named AccountsControllerV2. The second controller mimics the first, except for the fact that it works with the newer model and new data properties. In reality, it could also have entirely new operations or different plumbing behind existing ones.
  • For kicks and giggles, I also created a new model (Invoice) and controller (InvoicesControllerV1) just to show the flexibility of the controller selector.

2012.09.25webapi05

I created a class, HeaderVersionControllerSelector, that will be used at runtime to pick the right controller to respond to the request. Note that my example below is NOT efficiently written, but just meant to show the moving parts. After seeing what I do below, I strongly encourage you to read this great post and very nice accompanying Github code project that shows a clean way to build the selector.

Basically, there are a few key parts here. First, I created a dictionary to hold the controller (descriptions) and load that within the constructor. These are all the controllers that the selector has to choose from. Second, I added a helper method (thanks to the previously mentioned blog post/code) called “GetControllerNameFromRequest” that yanks out the name of the controller (e.g. “accounts”) provided in the HTTP request. Third, I implemented the required “GetControllerMapping” operation which simply returns my dictionary of controller descriptions. Finally, I implemented the required “SelectController” operation which determines the API version from the HTTP header (“X-Api-Version”), gets the controller name (from the previously created helper function), and builds up the full name of the controller to pull from the dictionary.

 /// <summary>
    /// Selects which controller to serve up based on HTTP header value
    /// </summary>
    public class HeaderVersionControllerSelector : IHttpControllerSelector
    {
        //store config that gets passed on on startup
        private HttpConfiguration _config;
        //dictionary to hold the list of possible controllers
        private Dictionary<string, HttpControllerDescriptor> _controllers = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="config"></param>
        public HeaderVersionControllerSelector(HttpConfiguration config)
        {
            //set member variable
            _config = config;

            //manually inflate controller dictionary
            HttpControllerDescriptor d1 = new HttpControllerDescriptor(_config, "AccountsControllerV1", typeof(AccountsControllerV1));
            HttpControllerDescriptor d2 = new HttpControllerDescriptor(_config, "AccountsControllerV2", typeof(AccountsControllerV2));
            HttpControllerDescriptor d3 = new HttpControllerDescriptor(_config, "InvoicesControllerV1", typeof(InvoicesControllerV1));
            _controllers.Add("AccountsControllerV1", d1);
            _controllers.Add("AccountsControllerV2", d2);
            _controllers.Add("InvoicesControllerV1", d3);
        }

        /// <summary>
        /// Implement required operation and return list of controllers
        /// </summary>
        /// <returns></returns>
        public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
        {
            return _controllers;
        }

        /// <summary>
        /// Implement required operation that returns controller based on version, URL path
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public HttpControllerDescriptor SelectController(System.Net.Http.HttpRequestMessage request)
        {
            //yank out version value from HTTP header
            IEnumerable<string> values;
            int? apiVersion = null;
            if (request.Headers.TryGetValues("X-Api-Version", out values))
            {
                foreach (string value in values)
                {
                    int version;
                    if (Int32.TryParse(value, out version))
                    {
                        apiVersion = version;
                        break;
                    }
                }
            }

            //get the name of the route used to identify the controller
            string controllerRouteName = this.GetControllerNameFromRequest(request);

            //build up controller name from route and version #
            string controllerName = controllerRouteName + "ControllerV" + apiVersion;

            //yank controller type out of dictionary
            HttpControllerDescriptor controllerDescriptor;
            if (this._controllers.TryGetValue(controllerName, out controllerDescriptor))
            {
                return controllerDescriptor;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Helper method that pulls the name of the controller from the route
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        private string GetControllerNameFromRequest(HttpRequestMessage request)
        {
            IHttpRouteData routeData = request.GetRouteData();

            // Look up controller in route data
            object controllerName;
            routeData.Values.TryGetValue("controller", out controllerName);

            return controllerName.ToString();
        }
    }

Nearly done. All that was left was to update the global.asax.cs file to ignore the default controller handling (where it looks for the controller name from the URI and appends “Controller” to it) and replace it with our new controller selector.

public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            //added to support runtime controller selection
            GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector),
                                                           new HeaderVersionControllerSelector(GlobalConfiguration.Configuration));
        }
    }

That’s it! Let’s try this bad boy out. First, I tried retrieving an individual record using the “version 1” API. Notice that I added an HTTP header entry for X-Api-Version.

2012.09.25webapi06

Did you also see how easy it is to switch content formats? Just changing the” Content-Type” HTTP header to “application/xml” resulted in an XML response without me doing anything to my service. Next, I did a GET against the same URI, but set the X-Api-Version to 2.

2012.09.25webapi07

The second version of the API now returns the “sub accounts” for a given account, while not breaking the original consumers of the first version. Success!

Summary

The ASP.NET Web API clearly multiple versioning strategies, and I personally like this one the best. You saw how it was really easy to carve out entirely new controllers, and thus new client experiences, without negatively impacting existing service clients.

What do you think? Are you a fan of version information in the URI, or are HTTP headers the way to go?

About these ads
Posted in: .NET, ASP.NET Web API, SOA