API Versioning in .NET MVC 4
- From the sandbox
- Tutorial
Good day.
With the advent of ASP.NET Web API, a convenient and powerful tool for creating an API for your site has appeared. But, as you know, over time, your API can change, be supplemented, or it can be completely redone from scratch. For compatibility with older clients, versioning must be implemented.
Unfortunately, at the moment, Microsoft has not provided a convenient and easy way to implement versioning. On the Internet you can find some information on this topic, but, as a rule, most of the solutions I have found come down to adding a parameter for the version to each request and processing it. I wanted to get a more flexible method for versioning, which will not clog the controller methods and save a lot of if else blocks. And the main criterion for me was the ability to have controllers with the same name for the same API methods, but versioned using namespaces.
At the same time, the ASP.NET MVC Web API has a fairly powerful mechanism in the form of the IHttpControllerSelector interface, with which you can implement versioning, leaving the code clean and understandable.
Let's see what came of it.
First of all, we need to configure routing correctly so that the version number is interpreted as a parameter (in the controller we will simply ignore it).
Thus, all our API methods will look like api / v {version} / {controller} / {id}, where {version} is the version number of the API. In fact, you can use not only numbers, but generally anything. The main thing is that we can distinguish API implementations by this parameter.
Next, you need to establish the correct request processing: selection and creation of controllers. This process looks very simple. The controller factory must know which controller it needs to create. This is precisely what the IHttpControllerSelector interface is for.
In most cases, we are completely satisfied with the standard DefaultHttpControllerSelector , so to implement versioning it is not necessary to completely write it from scratch.
To do this, we will relinquish DefaultHttpControllerSelector and override its main SelectController method. It is he who is responsible for the choice of the controller and provides the factory with a handle to the controller.
ControllerSelector requires the current HttpConfiguration as a parameter. Therefore, we register it in the IoC container with dependency. Most often I use Castle Windsor , so it’s used in the example.
Now we go directly to the process of selecting the appropriate controller in the SelecController method.
The controller factory expects the HttpControllerDescriptor descriptor from us , which consists of the name of the controller and its type.
To get the name of the controller, we can use the basic functionality of the DefaultHttpControllerSelector class.
If everything is simple and clear with the name, then to determine the type of controller we need to know the controllers available in our system. To do this, add a field and a method to calculate them. After that, our class looks like this:
We also need the version number from the request
Add a method to get the controller type by version
The method itself is extremely simple: from the list of controller types we need to select the controller lying in the namespace of the corresponding version of the API
It uses two custom extensions to filter controllers
Now we have everything we need to create a controller handle. Resulting Class:
As a result, we have a simple API versioning mechanism with the ability to have controllers of the form
If your API has not changed radically, then we only need to slightly adjust the filtering methods that would select the controller of the latest available version.
Thanks for attention.
With the advent of ASP.NET Web API, a convenient and powerful tool for creating an API for your site has appeared. But, as you know, over time, your API can change, be supplemented, or it can be completely redone from scratch. For compatibility with older clients, versioning must be implemented.
Unfortunately, at the moment, Microsoft has not provided a convenient and easy way to implement versioning. On the Internet you can find some information on this topic, but, as a rule, most of the solutions I have found come down to adding a parameter for the version to each request and processing it. I wanted to get a more flexible method for versioning, which will not clog the controller methods and save a lot of if else blocks. And the main criterion for me was the ability to have controllers with the same name for the same API methods, but versioned using namespaces.
At the same time, the ASP.NET MVC Web API has a fairly powerful mechanism in the form of the IHttpControllerSelector interface, with which you can implement versioning, leaving the code clean and understandable.
Let's see what came of it.
First of all, we need to configure routing correctly so that the version number is interpreted as a parameter (in the controller we will simply ignore it).
httpRoutes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v{version}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Thus, all our API methods will look like api / v {version} / {controller} / {id}, where {version} is the version number of the API. In fact, you can use not only numbers, but generally anything. The main thing is that we can distinguish API implementations by this parameter.
Next, you need to establish the correct request processing: selection and creation of controllers. This process looks very simple. The controller factory must know which controller it needs to create. This is precisely what the IHttpControllerSelector interface is for.
In most cases, we are completely satisfied with the standard DefaultHttpControllerSelector , so to implement versioning it is not necessary to completely write it from scratch.
To do this, we will relinquish DefaultHttpControllerSelector and override its main SelectController method. It is he who is responsible for the choice of the controller and provides the factory with a handle to the controller.
public class HttpControllerSelector : DefaultHttpControllerSelector
{
private readonly HttpConfiguration configuration;
public HttpControllerSelector(HttpConfiguration configuration) : base(configuration)
{
this.configuration = configuration;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
}
}
ControllerSelector requires the current HttpConfiguration as a parameter. Therefore, we register it in the IoC container with dependency. Most often I use Castle Windsor , so it’s used in the example.
container.Register(
Component.For().ImplementedBy().DependsOn(
Dependency.OnValue(GlobalConfiguration.Configuration)));
Now we go directly to the process of selecting the appropriate controller in the SelecController method.
The controller factory expects the HttpControllerDescriptor descriptor from us , which consists of the name of the controller and its type.
HttpControllerDescriptor(HttpConfiguration, String, Type)
To get the name of the controller, we can use the basic functionality of the DefaultHttpControllerSelector class.
var controllerName = GetControllerName(request);
If everything is simple and clear with the name, then to determine the type of controller we need to know the controllers available in our system. To do this, add a field and a method to calculate them. After that, our class looks like this:
public class HttpControllerSelector : DefaultHttpControllerSelector
{
private readonly HttpConfiguration configuration;
private readonly Lazy> controllerTypes;
public HttpControllerSelector(HttpConfiguration configuration) : base(configuration)
{
this.configuration = configuration;
controllerTypes = new Lazy>(GetControllerTypes);
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var controllerName = GetControllerName(request);
}
private static ConcurrentDictionary GetControllerTypes()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var types = assemblies
.SelectMany(a => a.GetTypes().Where(t => !t.IsAbstract &&
t.Name.EndsWith(ControllerSuffix, StringComparison.OrdinalIgnoreCase) &&
typeof (IHttpController).IsAssignableFrom(t)))
.ToDictionary(t => t.FullName, t => t);
return new ConcurrentDictionary(types);
}
}
We also need the version number from the request
object version;
request.GetRouteData().Values.TryGetValue("version", out version);
Add a method to get the controller type by version
var type = GetControllerType((string)version, controllerName);
The method itself is extremely simple: from the list of controller types we need to select the controller lying in the namespace of the corresponding version of the API
private Type GetControllerType(string version, string controllerName)
{
var query = controllerTypes.Value.AsEnumerable();
return query.ByVersion(version)
.ByControllerName(controllerName)
.Select(x => x.Value)
.Single();
}
It uses two custom extensions to filter controllers
public static IEnumerable> ByVersion(this IEnumerable> query, string version)
{
var versionNamespace = string.Format(CultureInfo.InvariantCulture, ".V{0}.", version);
return query.Where(x => x.Key.IndexOf(versionNamespace, StringComparison.OrdinalIgnoreCase) != -1);
}
public static IEnumerable> ByControllerName(this IEnumerable> query, string controllerName)
{
var controllerNameToFind = string.Format(CultureInfo.InvariantCulture, ".{0}{1}", controllerName, DefaultHttpControllerSelector.ControllerSuffix);
return query.Where(x => x.Key.EndsWith(controllerNameToFind, StringComparison.OrdinalIgnoreCase));
}
Now we have everything we need to create a controller handle. Resulting Class:
public class HttpControllerSelector : DefaultHttpControllerSelector
{
private readonly HttpConfiguration configuration;
private readonly Lazy> controllerTypes;
public HttpControllerSelector(HttpConfiguration configuration) : base(configuration)
{
this.configuration = configuration;
controllerTypes = new Lazy>(GetControllerTypes);
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
object version;
request.GetRouteData().Values.TryGetValue("version", out version);
var controllerName = GetControllerName(request);
var type = GetControllerType((string)version, controllerName);
return new HttpControllerDescriptor(configuration, controllerName, type);
}
private static ConcurrentDictionary GetControllerTypes()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var types = assemblies
.SelectMany(a => a.GetTypes().Where(t => !t.IsAbstract &&
t.Name.EndsWith(ControllerSuffix, StringComparison.OrdinalIgnoreCase) &&
typeof (IHttpController).IsAssignableFrom(t)))
.ToDictionary(t => t.FullName, t => t);
return new ConcurrentDictionary(types);
}
private Type GetControllerType(string version, string controllerName)
{
var query = controllerTypes.Value.AsEnumerable();
return query.ByVersion(version)
.ByControllerName(controllerName)
.Select(x => x.Value)
.Single();
}
}
public static class ControllerTypeSpecifications
{
public static IEnumerable> ByVersion(this IEnumerable> query, string version)
{
var versionNamespace = string.Format(CultureInfo.InvariantCulture, ".V{0}.", version);
return query.Where(x => x.Key.IndexOf(versionNamespace, StringComparison.OrdinalIgnoreCase) != -1);
}
public static IEnumerable> ByControllerName(this IEnumerable> query, string controllerName)
{
var controllerNameToFind = string.Format(CultureInfo.InvariantCulture, ".{0}{1}", controllerName, DefaultHttpControllerSelector.ControllerSuffix);
return query.Where(x => x.Key.EndsWith(controllerNameToFind, StringComparison.OrdinalIgnoreCase));
}
}
As a result, we have a simple API versioning mechanism with the ability to have controllers of the form
Controllers.Api.V1.UserController
Controllers.Api.V2.UserController
If your API has not changed radically, then we only need to slightly adjust the filtering methods that would select the controller of the latest available version.
Thanks for attention.