We have a REST API for our site. We've been versioning endpoints by prefixing the endpoint with the release version the endpoint was added or updated with. For example
- /v1/service-a
- /v2/service-b
- /v3/service-c
The problem with this approach is we have our client code calling out to different versions of the endpoints. For example a page may call out to /v3/service-c and /v1/service-a.
I'd like to set it up so that our developers can access the latest version of the API by just prefixing the endpoints with the latest version of the endpoints. Using the example above, the page would instead call out to /v3/service-c and /v3/service-a, and for service-a, the request would be forwarded to the action bound to /v1/service-a because that was the latest version of the service prior to /v3.
I know I could just manually add the routes explicitly in code, but that would get difficult with managing different versions of endpoints.
I know how to enumerate the versions from the route table and parse them; so that part is solved. However, what I'm not sure on is how exactly I go about intercepting the the routing so that calls to /v3/-service-a can be forwarded to the route I have setup for /v1/service-a.
Any ideas?
Imagine you declare your routes like this:
config.Routes.MapHttpRoute("defaultVersioned", "v{version}/{controller}/{id}",
new { id = RouteParameter.Optional }, new { version = @"\d+" });
config.Routes.MapHttpRoute("default", "{controller}/{id}",
new { id = RouteParameter.Optional });
You can now create separate controllers for separate versions, using a specific naming convention for example:
public class FooController : ApiController {}
public class FooV2Controller : ApiController {}
public class FooV3Controller : ApiController {}
Now, since version is part of your route, you can implement a custom controller selector, where you'd pick up the version from the route and select a relevant controller based on that.
public class VersionAwareControllerSelector : DefaultHttpControllerSelector
{
public VersionAwareControllerSelector(HttpConfiguration configuration) : base(configuration) { }
public override string GetControllerName(HttpRequestMessage request)
{
var controllerName = base.GetControllerName(request);
var versionFinder = new VersionFinder();
var version = versionFinder.GetVersionFromRequest(request);
if (version > 0)
{
return GetVersionedControllerName(request, controllerName, version);
}
return controllerName;
}
private string GetVersionedControllerName(HttpRequestMessage request, string baseControllerName, int version)
{
var versionControllerName = string.Format("{0}v{1}", baseControllerName, version);
HttpControllerDescriptor descriptor;
if (GetControllerMapping().TryGetValue(versionControllerName, out descriptor))
{
return versionControllerName;
}
throw new HttpResponseException(request.CreateErrorResponse(
HttpStatusCode.NotFound,
String.Format("No HTTP resource was found that matches the URI {0} and version number {1}",
request.RequestUri, version)));
}
}
This code uses a VersionFinder
helper class, which can be found here.
Then you just have to register the custom selector:
config.Services.Replace(typeof(IHttpControllerSelector), new VersionAwareControllerSelector(config));
For a full example - have a look here on Github, it's part of the ASP.NET Web API 2 Recipes book.