Geeks With Blogs

News This is the *old* blog. The new one is at blog.sixeyed.com
Elton Stoneman
This is the *old* blog. The new one is at blog.sixeyed.com

I had an interesting problem with API versioning. The API behaviour for a given resource was going to change between version 1.x and 1.y, but the controller logic was going to stay the same. Usually when your behaviour changes it's a big enough change that you need a new controller and you can direct the request appropriately - either using a version number in the route, or (my preference) with a version in the request header and a custom IHttpControllerSelector.

But in this case the business logic in the controller was the same for 1.x and 1.y, but the pipeline logic was different. This was a POST request which accepted a GZipped payload, so the 1.x version used a delegating handler which was scoped to the route:

            routes.MapHttpRoute(
                name: "GZippedReports",
                routeTemplate: "reports",
                defaults: new { controller = "Reports" },
                handler: new CompressedRequestHandler{ InnerHandler = new HttpControllerDispatcher(GlobalConfiguration.Configuration) });

 

That's a nice approach, because it means we're only interrupting the default pipeline for this one controller, which is the only one that will get GZip requests. That way we save the cost of checking every incoming request to see if it's compressed, but where we expect the requests will be compressed we're using the right part of the pipeline, and separating the formatting logic from the controller logic.

For version 1.y we'd be using the same endpoint (/reports) and the same controller, but with an additional handler to increase security around the API. We did something like adding a digital signature to the request and checking it in the API to ensure the payload hadn't been tampered with.

Assuming the request signature was OK, we'd go on to do the same thing - decompress it and move on to the controller, so the route would be the same as for 1.x but with an additional outer handler:

            routes.MapHttpRoute(
                name: "SignedGZippedReports",
                routeTemplate: "reports",
                defaults: new { controller = "Reports" },
                handler: new SignedRequestHandler { InnerHandler = new CompressedRequestHandler{ InnerHandler = new HttpControllerDispatcher(GlobalConfiguration.Configuration) } });

 

But we want to keep those routes separate for 1.x and 1.y, and by doing it with Route Constraints we can keep all the logic in the route mapping and support versioning by both request header and query string, and keep everyone happy.

Here's how the final route mapping looks:

            routes.MapHttpRoute(
                name: "GZippedReports",
                routeTemplate: "reports/{version}",
                defaults: new { controller = "Reports", version = RouteParameter.Optional },
                handler: new CompressedRequestHandler{ InnerHandler = new HttpControllerDispatcher(GlobalConfiguration.Configuration) },
                constraints: new {version = new ApiVersionRouteConstraint { IsDefault = true, Maximum = 1.3 }});

            routes.MapHttpRoute(
                name: "SignedGZippedReports",
                routeTemplate: "reports/{version}",
                defaults: new { controller = "Reports", version = RouteParameter.Optional },
                handler: new SignedRequestHandler { InnerHandler = new CompressedRequestHandler{ InnerHandler = new HttpControllerDispatcher(GlobalConfiguration.Configuration) } },
                constraints: new { version = new ApiVersionRouteConstraint { Minimum = 1.4 } });

 

The route constraints disambiguate requests to /reports, by using the optional parameter {version}, and a custom route constraint specifying the minimum or maximum API version for each route. In my case the routes are using the same controller with different handler pipelines, but you could use different controllers too.

With these routes, any request which specifies an API version up to 1.3 - or which doesn't specify an API version - will use the decompress pipeline; any request which specifies version 1.4 or higher will use the check-signature-then-decompress pipeline.

ApiRouteConstraint is a custom implementation of IHttpRouteConstraint. The Match() method gets called for every route that is a potential match. If it returns true, WebAPI uses that route; if it returns false, WebAPI carries on checking other possible matches. You can also throw an HttpResponseException from the constraint to send a specific response back to the client - useful if they send a version header which is in the wrong format.

The Match implementation for the version route constraint is very simple. It assumes a versioning format of <major>.<minor> where new releases are always a higher number, and it prefers clients requesting the version number in the header, although it supports having the version in the URL. The constraint allows a route to declare itself as the default, so it will be used if the client doesn't specify a version in the request:

        public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary values, HttpRouteDirection routeDirection)
        {   
            var version = GetVersion(request.Headers) ?? GetVersion(request.RequestUri) ?? 0;
            
            if (version == 0 && IsDefault)
                return true;

            return (version >= Minimum && (Maximum == 0 ||version <= Maximum));
        }

 

It's a simple way to get versioning into your API, and to express the version as part of the route table (you can also use the constraint in attribute versioning). Full code and tests on github here: sixeyed/webapi-routeconstraintversioning.

Posted on Thursday, December 18, 2014 9:07 AM REST , WebAPI | Back to top


Comments on this post: Versioning in WebAPI using Route Constraints

# Juegos Yepi
Requesting Gravatar...
It is very attractive
http://www.yepi2015.com/tag/juegos-yepi
Left by JuegosYepi on Dec 23, 2014 2:21 PM

# re: Versioning in WebAPI using Route Constraints
Requesting Gravatar...
Very nice explanation and example of how to use route constraints!
Left by Greg on Dec 30, 2014 10:51 PM

# re: Versioning in WebAPI using Route Constraints
Requesting Gravatar...
I have a query in my usecase. Please help me to solve.

This is my scenario :

I have to do API versioning. I am using REST APIs with Restlet framework. I am confused how to implement the versioning in Java Source code.

If my API looks like this : http://localhost:8080/example.com/v2/hello
Then In my source code how to map the version with appropriate functionality?

For example :

If(version == 1) {//Do Version 1 Source Code}
else If(version == 2) {//Do Version 2 Source Code}
else If(version == 3) {//Do Version 3 Source Code} ………..

If I did like this, Then why we are going for Versioning. We can implement Manually Right? And also If I have to add some 100 versions, then the code looks uglier and it becomes lengthier.
I get confused how to map in Source Code. Please Clarify my doubt.

I have read that we can use annotations for handling API versioning. But I don't know what are the annotations available in restlet framework. Please let me know.

Regards,
Geetha.
Left by Geetha on Apr 20, 2015 2:07 PM

Your comment:
 (will show your gravatar)


Copyright © Elton Stoneman | Powered by: GeeksWithBlogs.net