API versioning is very common requirement of API developer. In the very initial stage you will require to share stubs and then later you have to maintain version. A very trivial and simple solution is to have different URL for different versions and have different handlers(controllers) for each.
Ofcourse the approch will work but there are features available in WebApi by which we can avoid having multiple URLs and make caller's life easy.
As we know that in MVC and WEBAPI, there is controller resolved which resolves the controller based on mapping. I will try to use the same thing to put our logic in between by which we can resolve the controller based on our logic.
For this first of all we need to create a controller selector -
public class CustomControllerSelector : DefaultHttpControllerSelector
{
private HttpConfiguration _config { get; set; }
public CustomControllerSelector(HttpConfiguration config) : base(config)
{
_config = config;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
}
}
In WebApi we DefaultHttpControllerSelector is the default Httpcontrollerselecotr. Here we have created our own selector and inheritied from Default one. DefaultControllerSelector has parametrized constructor which takes Httpconfiguration as input, so we will follow the same and here we have basic implementation in place.
Lets start writing something in SelectController, because this is the method which resolves the controller and returns HttpControllerDescriptor object.
1) lets get all the mapping availble in the application -
var controllers = GetControllerMapping();
If you see the screenshot, we get the mapping of Controller name with the actual controller object.
2) Get the parameters or routing info - var routeData = request.GetRouteData();
Based on the request URL, how it mapped with the mapping registered. For example if the following mapping is registered -
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
then based on URL matched, what is the value of controller or id, we get this kind of values in the routeData.
3) Based on routeData, get he value of current controller -
var controllername = (string)routeData.Values["controller"];
4) If we have followed attribute based mapping , then we will not get the controller named with above approach, hence we will just return the default mapping -
if (string.IsNullOrWhiteSpace(controllername))
{
return base.SelectController(request);
}
5) Based on controller name, we can get the controller instance from the controller collection(step 1).
6) There are different ways of getting the version information from the URL- might be query parameter, or header. Here is the code of getting this version from query parameter -
private string GetVersionFromQueryString(HttpRequestMessage request)
{
var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
var version = query["ver"];
if (version != null)
{
return version;
}
return "";
}
7) once we have version we just have to form the name of controller accordingly and return the controller instance. Here is the example of version to be "stub", then we make the controller name as current controller + "_stub" and return it back -
string newName = string.Empty;
if ("stub".Equals(version))
{
newName = string.Concat(controllername, "_stub", "");
HttpControllerDescriptor versionedDescriptor;
if (controllers.TryGetValue(newName, out versionedDescriptor))
{
return versionedDescriptor;
}
}
Here is the complete code for the reference -
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var controllers = GetControllerMapping();
var routeData = request.GetRouteData();
var controllername = (string)routeData.Values["controller"]; // values determined by route
HttpControllerDescriptor descriptor;
if (string.IsNullOrWhiteSpace(controllername))
{
return base.SelectController(request);
}
else if (controllers.TryGetValue(controllername, out descriptor))
{
var version = GetVersionFromQueryString(request);
string newName = string.Empty;
if ("stub".Equals(version))
{
newName = string.Concat(controllername, "_stub", "");
HttpControllerDescriptor versionedDescriptor;
if (controllers.TryGetValue(newName, out versionedDescriptor))
{
return versionedDescriptor;
}
}
return descriptor;
}
return null;
}
We just have to register this selector in our webapi config -
config.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector(config));
Thats all, the versioning is done, you can write your own logic on which case you want to pass which controller instance.
Ofcourse the approch will work but there are features available in WebApi by which we can avoid having multiple URLs and make caller's life easy.
As we know that in MVC and WEBAPI, there is controller resolved which resolves the controller based on mapping. I will try to use the same thing to put our logic in between by which we can resolve the controller based on our logic.
For this first of all we need to create a controller selector -
public class CustomControllerSelector : DefaultHttpControllerSelector
{
private HttpConfiguration _config { get; set; }
public CustomControllerSelector(HttpConfiguration config) : base(config)
{
_config = config;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
}
}
In WebApi we DefaultHttpControllerSelector is the default Httpcontrollerselecotr. Here we have created our own selector and inheritied from Default one. DefaultControllerSelector has parametrized constructor which takes Httpconfiguration as input, so we will follow the same and here we have basic implementation in place.
Lets start writing something in SelectController, because this is the method which resolves the controller and returns HttpControllerDescriptor object.
1) lets get all the mapping availble in the application -
var controllers = GetControllerMapping();
If you see the screenshot, we get the mapping of Controller name with the actual controller object.
2) Get the parameters or routing info - var routeData = request.GetRouteData();
Based on the request URL, how it mapped with the mapping registered. For example if the following mapping is registered -
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
then based on URL matched, what is the value of controller or id, we get this kind of values in the routeData.
3) Based on routeData, get he value of current controller -
var controllername = (string)routeData.Values["controller"];
4) If we have followed attribute based mapping , then we will not get the controller named with above approach, hence we will just return the default mapping -
if (string.IsNullOrWhiteSpace(controllername))
{
return base.SelectController(request);
}
5) Based on controller name, we can get the controller instance from the controller collection(step 1).
6) There are different ways of getting the version information from the URL- might be query parameter, or header. Here is the code of getting this version from query parameter -
private string GetVersionFromQueryString(HttpRequestMessage request)
{
var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
var version = query["ver"];
if (version != null)
{
return version;
}
return "";
}
7) once we have version we just have to form the name of controller accordingly and return the controller instance. Here is the example of version to be "stub", then we make the controller name as current controller + "_stub" and return it back -
string newName = string.Empty;
if ("stub".Equals(version))
{
newName = string.Concat(controllername, "_stub", "");
HttpControllerDescriptor versionedDescriptor;
if (controllers.TryGetValue(newName, out versionedDescriptor))
{
return versionedDescriptor;
}
}
Here is the complete code for the reference -
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var controllers = GetControllerMapping();
var routeData = request.GetRouteData();
var controllername = (string)routeData.Values["controller"]; // values determined by route
HttpControllerDescriptor descriptor;
if (string.IsNullOrWhiteSpace(controllername))
{
return base.SelectController(request);
}
else if (controllers.TryGetValue(controllername, out descriptor))
{
var version = GetVersionFromQueryString(request);
string newName = string.Empty;
if ("stub".Equals(version))
{
newName = string.Concat(controllername, "_stub", "");
HttpControllerDescriptor versionedDescriptor;
if (controllers.TryGetValue(newName, out versionedDescriptor))
{
return versionedDescriptor;
}
}
return descriptor;
}
return null;
}
We just have to register this selector in our webapi config -
config.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector(config));
Thats all, the versioning is done, you can write your own logic on which case you want to pass which controller instance.