Introduction:
This article explains about the
different types of API Versioning, its uses along with code samples on
achieving the same. Versioning means, calling different Controller/Methods for the same URI based on the
parameters coming the request URL.
Necessity of API Versioning:
For an example, you are developing a
Service, which will be consumed by Client A & B. Now Client B wants
additional functionalities on top of the existing service.
Without affecting Client A, we need to
add additional functionalities in existing service with minimal changes. API
versioning will help us to do the same.
Without Versioning, it is
challengeable to provide the same URI (i.e. URL with same Controller &
Action) to different vendors having different Business functionalities in it.
Types of API Versioning:
·
URI – This is
straightforward approach, URI versioning uses routing to achieve api
versioning. ·
Query string
parameter – Query parameters will added to the URL, bases on the value in the
query string appropriate action will trigger. ·
Custom Header
parameter – Custom header key added to the request, so there will be no URI
change required. Based on the value in the customer header key, appropriate
action will trigger. ·
Accept Header
parameter – Like customer header, custom value will passed in accept header in
the request, no URI change required.
URI Versioning:
Step -1
Register the map routing in
WebApiConfig for all the versions like below
config.Routes.MapHttpRoute(
name: "EmployeeV1",
routeTemplate: "api/V1/{controller}/{action}/{id}",
defaults: new { controller = "EmployeeV1", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "EmployeeV2",
routeTemplate: "api/V2/{controller}/{action}/{id}",
defaults: new { controller="EmployeeV2", id = RouteParameter.Optional }
);
Above code registers two versions V1
and V2, based on the version value in the URL, system will redirect to
appropriate controller. Testing API
Versioning
In the below screenshot, URI contains
version information i.e V1, it executes Index action in EmployeeV1 controller.
In the below screenshot, URI contains
version information i.e V2, it executes Index action in EmployeeV2 controller.
The only change in the above two
example is version value in the URL (V1, V2).
Versioning through Query String:
Query string versioning is also more
or less similar to URI versioning, URI get changed,
Step-1
Create custom controller selector
class, write logic to read query string value and pick appropriate controller
based on the query string value as shown below,
public class QueryStringSelectorController : DefaultHttpControllerSelector
{
HttpConfiguration _config;
public
QueryStringSelectorController(HttpConfiguration config) : base(config)
{
_config = config;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
//returns all possible API Controllers
var
controllers = GetControllerMapping();
//return the information about the route
var
routeData = request.GetRouteData();
var
versionQueryString = HttpUtility.ParseQueryString(request.RequestUri.Query);
string apiVersion = "Home";
//Get the query string value
if
(versionQueryString["version"]
!= null)
{
apiVersion = Convert.ToString(versionQueryString["version"]);
}
var
controllerName = "";
if
(apiVersion == "V1")
{
controllerName = "EmployeeV1";
}
else
{
controllerName = "EmployeeV2";
}
HttpControllerDescriptor controllerDescriptor;
if
(controllers.TryGetValue(controllerName, out
controllerDescriptor))
{
return controllerDescriptor;
}
return null;
}
}
Step-2
Register custom action selector as
default action selector in webApi.Config.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Services.Replace(typeof(IHttpControllerSelector), new QueryStringSelectorController(config));
}
}
In the below screenshot, URL has the
query string parameter Version with Value “V1”, it executes the EmployeeV1
controller action.
In the below screenshot, URL has the
query string parameter Version with Value “V2”, it executes the EmployeeV2
controller action.
Custom Header Parameter:
Custom header provides the facility to
do versioning of API without changing the URI. With this approach, request
content will inspected and based on the value of the version key, appropriate
action fetched.
Step-1
Extending ApiControllerActionSelector
to your new class as shown below
public class CustomActionSelector : ApiControllerActionSelector
{
}
Step-2
Register custom action selector as
default action selector in webApi.Config.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Services.Replace(typeof(IHttpActionSelector), new CustomActionSelector());
}
}
Step-3
Override Select Action method in ApiControllerActionSelector to inspect
request header and selected appropriate action.
public class CustomActionSelector : ApiControllerActionSelector
{
public override HttpActionDescriptor SelectAction(HttpControllerContext context)
{
var request = new HttpMessageContent(context.Request).HttpRequestMessage;
var routeData = request.GetRouteData();
string customHeaderForVersion = "X-Demo-Version";
string apiVersion = "";
if
(request.Headers.Contains(customHeaderForVersion))
{
apiVersion =
request.Headers.GetValues(customHeaderForVersion).FirstOrDefault();
}
var currentActionName = routeData.Values["action"].ToString();
string actionName = currentActionName + "_" + apiVersion;
var selectedAction =
context.ControllerDescriptor.ControllerType.GetMethods().Where(p => p.Name
== actionName).FirstOrDefault();
if (selectedAction != null)
{
return new ReflectedHttpActionDescriptor(context.ControllerDescriptor,
selectedAction);
}
return base.SelectAction(context);
}
}
Above method will called for each
service request to select action. In our example custom version key is
“X-Demo-Version”
HomeController having two actions Index_V1 and Index_V2, above action selector
method will parse the request header, if the custom version key is available in
the request then it will append the version value with the action name to
select appropriate action to do versioning without changing service url.
public class HomeController : ApiController
{
[HttpGet]
public string Index_V1()
{
return "Version One in Home
controller";
}
[HttpGet]
public string Index_V2(int id)
{
return "Version two Id= " + id.ToString() + " in Home
controller";
}
}
Testing versioning
Add request header “X-Demo-Version”
and test the versioning with providing value as V1 or V2 as shown below, system
will fetch appropriate action based on value provided.
In some cases, we may want to change
the controller based on custom header value to do api versioning, that be
achieved with help of DefaultHttpControllerSelector,
we will see below how to implement this approach.
Step-1
Extending ApiControllerActionSelector
to your new class as shown below
public class CustomSelectorController: DefaultHttpControllerSelector
{
}
Step-2
Register custom controller selector as
default controller selector in webApi.Config.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Services.Replace(typeof(IHttpControllerSelector), new CustomSelectorController(config));
config.Services.Replace(typeof(IHttpActionSelector), new CustomActionSelector());
}
}
Step-3
Override Select Action method in SelectController to inspect request header and
selected appropriate controller.
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var controllers = GetControllerMapping();
var routeData = request.GetRouteData();
var controllerName = routeData.Values["controller"].ToString();
string apiVersion = "1";
string customHeaderForVersion = "X-Demo-Version";
if
(request.Headers.Contains(customHeaderForVersion))
{
apiVersion =
request.Headers.GetValues(customHeaderForVersion).FirstOrDefault();
}
if (apiVersion == "Home")
{
controllerName = "Home";
}
else
{
controllerName = "Account";
}
HttpControllerDescriptor controllerDescriptor;
if (controllers.TryGetValue(controllerName,
out controllerDescriptor))
{
return controllerDescriptor;
}
return null;
}
}
Above method will inspect request
header, if custom header available then it will check that value and fetch
appropriate controller.
|