Web API versioning using QueryString

by Sachin Singh


Posted on Monday, 18 May 2020

Tags: versioning using query string web api versioning how to pass version number in query string custom controller selector method implementation in web api

This is the continuation of the Web API Versioning Series, in the previous two articles we have learned
  • why Versioning is required Read here and
  • Versioning using URI Read here

In this article we will learn how to implement versioning using QueryString. Till now we have two API Controllers :-
  1. SilverEmployeeController, which returns Employee's Name ,Id and Salary.
  2. GoldEmployeeController , which returns Employee's First Name ,Last Name, Id and Salary.

Our Clients know nothing about the real implementation, they just know that they are consuming EmployeeService and there exist two versions of the service. In order to consume the service they have to pass the version number too along with the URL, it's our duty to tell our client how they can pass the version number. There are many ways with which version number can be passed like:-
  1. In the Form of routed data .(versioning using URI)
  2. In the form of query string (versioning using query string)
  3. In the form of Custom Header (versioning using custom header)
  4. In the form of Accept Header (versioning using accept header)
  5. In the form of Custom Media Types (versioning using Custom media types).

Web API versioning using query string simply means the clients are going to specify the version number as the query string parameter like below

Versioning using query string
Passing version number as query string

So, our work is to tell web API to select the appropriate controller based on the version number specified by the client as a query string parameter.
  • If the client passes v=1, then we have to tell web API to select SilverEmployeeService Controller.
  • If the client passes v=2, then we have to tell web API to select GoldEmployeeService Controller.

In the previous case when the client was passing version number as a routed data value, telling web API to select an appropriate controller based on the version number was easy through routing, but when the version number is being passed as a query string parameter, routing is obviously not going to help, we can't define any route based on the query string value.

So, we have to dive deep and try to understand how exactly web API selects controller when the request is issued to a Web API service and is there any way to tweak the default implementation for controller selection.

How Web API Selects Controller

let us understand how is a controller selected when a request is issued to the following URI http://localhost:1234/api/EmployeeService/3
  • The Web API has a class called DefaultHttpControllerSelector, which is responsible for contoller mapping.
  • Actually , the "DefaultHttpControllerSelector" class has a method called "SelectController()" that collects the information from the URI and selects the appropriate Controller for processing the request.

Now ,in the URI we have following information
  1. Name of the Controller - EmloyeeService
  2. Value of the Id parameter- 3.

So from the URI, the SelectController() method takes the name of the controller in this case "EmployeeService" and finds " EmployeeServiceController" and returns it. This is the default implementation that works behind the scene.

But ,what we want ?

for the URL http://localhost:1234/EmployeeService?v=1 ,we want the web API to select the SilverEmployeeController , but we know the web will try to find EmployeeServiceController as it gets EmployeeSrvice as the Controller name from the URI , and since no such controller exists in our application so error will be thrown.

Now ,what else we could do to tell the web api to select controller based on the querystring parameter ( version number )

The DefaultHttpControllerSelector class allows us to extend the SelectController() method as it is marked as virtual. So, in order to tell the web api to select controller based on the query string value, we have to extend the SelectController() method and give it our own custom implementation.
Step 1.Add a folder in the solution and name it Custom.
step 2.Add a class file and name it CustomControllerSelector.
Step 3.Inherit this class from DefaultHttpControllerSelector and override the SelectController() method ,like below.


  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Net.Http;
  using System.Text.RegularExpressions;
  using System.Web;
  using System.Web.Http;
  using System.Web.Http.Controllers;
  using System.Web.Http.Dispatcher;

   namespace MyFirstAPIProject.Custom
   {
    
        public class CustomControllerSelector : DefaultHttpControllerSelector
        {
            private HttpConfiguration _config;
            public CustomControllerSelector(HttpConfiguration config)
                : base(config)
            {
                _config = config;
            }

            public override HttpControllerDescriptor
                SelectController(HttpRequestMessage request)
            {
                // Get all the available Web API controllers
                var controllers = GetControllerMapping();
                // Get the controller name and parameter values from the request URI
                var routeData = request.GetRouteData();

                // Get the controller name from route data.
                // The name of the controller in our case is "EmployeeService"
                var controllerName = routeData.Values["controller"].ToString();

                // Default version number to 1
                string versionNumber = "1";
                var versionQueryString = HttpUtility.ParseQueryString(request.RequestUri.Query);
                

                if (versionQueryString["v"] != null)
                {
                    versionNumber = versionQueryString["v"];
                }

                if (versionNumber == "1")
                {
                    // if version number is 1, then prepend "Silver" to the controller name.
                    // So at this point the, controller name will become SilverEmployeeService
                    controllerName = "Silver"+controllerName;
                }
                else
                {
                    // if version number is 2, then prepend "Gold" to the controller name.
                    // So at this point the, controller name will GoldEmployeeService
                    controllerName ="Gold"+controllerName;
                }

                HttpControllerDescriptor controllerDescriptor;
                if (controllers.TryGetValue(controllerName, out controllerDescriptor))
                {
                    return controllerDescriptor;
                }

                return null;
            }
        }
    
    }

The implementation is simple and straight forward, we are getting version number from querystring and accordingly changing the controller Name.

Step 4.Tell the web API to use CustomControllerSelector instead of the Default one ,by replacing the IHttpControllerSelector with CustomControllerSelector instance in WebApiConfig.CS file which resides under the App_Start folder.


   namespace MyFirstAPIProject
    {
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            
          config.Services.Replace(typeof(IHttpControllerSelector),
          new CustomControllerSelector(config));

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

          
        }
    }

   
  }

Now , run the application and fire two get request on the behalf of two different client like below and test the results.

Getting appropriate result based on version number
Versioning using query string

As you can see ,for the query string v=2 we are getting list of GoldEmployee back ,it means our custom controller selector is successfully working and versioning is successful.