Home > OWIN, Web API > Implementing Web API Versioning using OWIN

Implementing Web API Versioning using OWIN

In my previous post "Designing Web API Versioning" I covered some of the design aspects of versioning and in this post I am going to show you how to implement versioning in Web APIs using OWIN.

First I am going to define a simple interface so that we can plug this in the OWIN Pipeline and it allows a nice separation of Concerns and promotes Single Responsibility principle.

IVersionSelector.cs

public interface IVersionSelector
{
    string GetVersion(HttpRequestMessage request, IHttpRoute route);
}

As you can see the interface is a pretty straight forward, it has dependency on the HttpRequestMessage as well as IHttpRoute and returns the version number. Also lets take care of some of the "Magic String" problem by creating a constant class as show below.

OwinConstants.cs

public static class OwinConstants
{
    public const string GET = "GET";
    public const string POST = "POST";
    public const string PUT = "PUT";

    public const char EqualsDelimiter = '=';
    public const char Period = '.';
    public const char Underscore = '_';
    public const char Semicolon = ';';
    

    public const string Version = "version";
    public const string VersionPrefix = "V";
    public const string RequestMethod = "owin.RequestMethod";
    public const string Controller = "controller";
    public const string ContentType = "Content-Type";
}

In that post I also mentioned that the version should be passed as an Accept header if it’s a GET and Content-Type header if it is a POST or PUT. However, when it comes to DELETE you don’t need a version as you are DELETING a resource and from a resource point of view it doesn’t make any sense to specify a version to be deleted. For example at a resource like /api/customer/1 it is very clear that you are looking for customer resource with an id 1 and for delete you just want to delete that id irrespective of what version you are supporting for that Web API.

So our logic will do a simple check from the OWIN pipeline to identity what the request type is and based on the type we extract the relevant header either from the Accept header or from the Content-Type header.

Another important thing to consider is that what do we do if there are no headers supplied? Depending on your requirements and discussion you may implement it differently. However, in my implementation I will use the one which is defined in the route table as a fall back value.

Lets put this all together in the code to see how it looks.

DefaultVersionSelector.cs

public class DefaultVersionSelector : IVersionSelector
{
    private const string versionFormat = "version={0}";
    public string GetVersion(HttpRequestMessage request, IHttpRoute route)
    {
        object routeVersion = string.Empty;
        string headerVersion = string.Empty;

        var owinEnvironment = request.GetOwinEnvironment();

        if (request.Method.Method == OwinConstants.POST || 
            request.Method.Method == OwinConstants.PUT)
        {
            headerVersion = GetVersionFromContentType(request);

            if (!string.IsNullOrEmpty(headerVersion))
            {
                return headerVersion;
            }
        }

        if (request.Method.Method == OwinConstants.GET)
        {
            headerVersion = GetVersionFromAcceptHeaderVersion(request);

            if (!string.IsNullOrEmpty(headerVersion))
            {
                return headerVersion;
            }
        }

        route.Defaults.TryGetValue(OwinConstants.Version, out routeVersion);
        return routeVersion as string;
    }

    private string GetVersionFromContentType(HttpRequestMessage request)
    {
        var versionList = new List<string>();
        var version = string.Empty;

        if (request.Content.Headers.ContentType == null)
        {
            throw new HttpResponseException(request.CreateResponse(HttpStatusCode.NotAcceptable));
        }

        request.Content.Headers.ToList().ForEach(x =>
        {
            if (x.Key.Contains(OwinConstants.ContentType))
            {
                foreach (var item in x.Value.ToList())
                {
                    if (item.Contains(OwinConstants.Version))
                    {
                        version = item.Split(OwinConstants.EqualsDelimiter)[1];
                    }
                }
            }
        });


        return version;
    }

    private string GetVersionFromAcceptHeaderVersion(HttpRequestMessage request)
    {
        string version = string.Empty;

        request.Headers.Accept.ToList().ForEach(headerValue =>
        {
             headerValue.Parameters.ToList().ForEach(v  =>
             { version = v.Value; });
        });

        return version;
    }
}

Pretty straight forward and now we will hook the above version selector into the ASP.NET Web API pipeline by simply inheriting from the DefaultHttpControllerSelector and overriding the quot&;SelectController" method, it’s that simple.

DefaultVersionControllerSelector.cs

public class DefaultVersionControllerSelector : DefaultHttpControllerSelector
{
    private const string versionFormat = "version={0}";

    private HttpConfiguration config;
    private IVersionSelector versionSelector;
    public DefaultVersionControllerSelector(HttpConfiguration config, 
                                            IVersionSelector versionSourceFactory) 
                                            : base(config)
    {
        this.config = config;
        this.versionSelector = versionSourceFactory;
    }

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

        var controllerName = routeData.Values[OwinConstants.Controller].ToString();
        var owinEnvironment = request.GetOwinEnvironment();

        var version = versionSelector.GetVersion(request, routeData.Route);

        version = version.Replace(OwinConstants.Period, OwinConstants.Underscore);

        HttpControllerDescriptor controllerDescriptor;

        owinEnvironment.Add(OwinConstants.Version, new string[] { string.Format(versionFormat,version) });
        request.SetOwinEnvironment(owinEnvironment);

        var versionedControllerName = string.Concat(controllerName,
                                                    OwinConstants.VersionPrefix,
                                                    version);

        HttpControllerDescriptor versionedControllerDescriptor;

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

        if (!string.IsNullOrEmpty(version) && versionedControllerDescriptor == null)
        {
            throw new HttpResponseException(
                          request.CreateResponse(HttpStatusCode.NotAcceptable));
        }

        var defaultControllerName = routeData.Route.Defaults[OwinConstants.Controller].ToString();

        controllers.TryGetValue(defaultControllerName, out controllerDescriptor);

        return controllerDescriptor;
    }
}

As you can see from the above code, first we extract controller name from the route data and then extract the OWIN environment variable from the HTTPRequestMessage object. We also took care of minor version of API by checking for a period delimiter in the version.

If we happen to find one then we replace the period delimiter with an underscore, as this will allow us to map the version to a valid C# controller class. For example if the version is specified as "version=1.1" for a controller name "values" then the output controller name will be "Values1_1Controller".

After this we try to get the list of all the controllers using the base class and check if we can create a controller using the route value, controller name and the version. If we succeed we return the controller descriptor as is otherwise we fall back to the default supported version of the controller which we are going to specify in routing configuration. I know the above explanation can be bit confusing and hard to understand but don’t worry it will all come together once I show you the routing configuration.

Lets create two controller and I’ll just use the run-of-the-mill "ValuesController" for simple illustration and the name of the two versioned controllers are going to be Values1Controller for version 1 and Values1_1Controller for version 1.1 as shown below.

ValuesV1Controller.cs

public class ValuesV1Controller : ApiController
{
    public IEnumerable<string> Get()
    {
        return new string[] { "value1-Version1", "value2-version1" };
    }
}

ValuesV1_1Controller.cs

public class ValuesV1_1Controller : ApiController
{
    public IEnumerable<string> Get()
    {
        return new string[] { "value1-Version1.1", "value2-version1.1" };
    }
}

And this is where the routing magic happens in our OWIN startup class.

Startup.cs

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var config = new HttpConfiguration();

        config.Routes.MapHttpRoute(
            "DefaultApi",
            "api/{controller}/{id}",
            new {   id = RouteParameter.Optional, 
                    version= "1" });

        config.Services.Replace(typeof(IHttpControllerSelector), 
                                       new DefaultVersionControllerSelector(config, 
                                           new DefaultVersionSelector()));

        app.UseWebApi(config);
    }
}

Now I am going to use POSTMAN to request two GETS one for version 1 and for version 1.1 as shown below.

VERSION 1
Web API Versioning

VERSION 1.1
Versioning test in POSTMAN

And as you can see from the returned data that the version selector is working correctly and we are hitting the right controller.

Advertisements
  1. July 12, 2015 at 6:42 pm

    Reblogged this on Dinesh Ram Kali..

  1. July 31, 2015 at 11:03 pm
  2. September 8, 2015 at 5:57 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: