Web API Versioning using URI

by Sachin Singh


Posted on Saturday, 16 May 2020

Tags: Web api versioning using URI How to perform versioning in web api with routed data

To understand web API versioning, first, we should have an existing service, later we will see, why versioning is required and how to create a newer version of the service.

so first, let's create a Service, for now, call it EmployeeService, and suppose this service exposes Employee's Name, Id and Salary to the outside world.

So, open visual studio and create a web API Controller, name it as EmployeeService and implement get methods as shown below.


 namespace MyFirstAPIProject.Controllers
    {
    public class EmployeeServiceController : ApiController
    {
        public List<Employee> Load()
        {

            List<Employee> Employees = new List<Employee>(){
                new Employee(){Id=1,Name="Sachin",Salary=600000},
                 new Employee(){Id=2,Name="Arjun",Salary=350000},
                  new Employee(){Id=3,Name="Vikash",Salary=500000},
                   new Employee(){Id=4,Name="Abhijit",Salary=45000},
            };
            return Employees;
        }

      public HttpResponseMessage Get()
        {
           try
            {
                var Employees = Load();
               
                if (Employees != null)
                {
                    return Request.CreateResponse(HttpStatusCode.OK, Employees);
                }
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No result found");
            }
            catch
            {
                return new HttpResponseMessage(HttpStatusCode.InternalServerError);
            }

        }

        public HttpResponseMessage Get(int id)
        {
             try
             {
                 Employee emp = Load().Where(x => x.Id == id).SingleOrDefault();
                 if (emp != null)
                 {
                     return Request.CreateResponse(HttpStatusCode.OK, emp);
                 }
                 return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Employee with id " + id + " Not found");

             }
             catch
             {
                 return new HttpResponseMessage(HttpStatusCode.InternalServerError);
             }
        }
       
      }
    }

So, this is our existing service, we have a lot of satisfied clients who are consuming this service and are happily using it in their application. But then we started receiving emails from lots of clients to upgrade the service.

Now many of the new clients and some existing clients are asking us to expose employee First name and Last Name too instead of their full name(Name). In such a situation, if we modify our service then the existing client may face problems and their application may break. so, we have decided to implement a newer version of the service.

So .let's create a new version of the service.
  1. create a model class and name it GoldEmployee .
  2. Create a Controller and name it GoldEmployeeService.
  3. Implement the Get methods for the same.
  4. Change the Older Controller Name from EmployeeService to SilverEmployeeService.


  namespace MyFirstAPIProject.Models
   {
    public class GoldEmployee
     {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Salary { get; set; }
     }
   }

  namespace MyFirstAPIProject.Controllers
   {
    public class GoldEmployeeServiceController : ApiController
    {
        public List<GoldEmployee> Load()
        {
            List<GoldEmployee> emps = new List<GoldEmployee>(){
                new GoldEmployee(){Id=1,FirstName="sachin",LastName="singh",Salary=4500000},
                new GoldEmployee(){Id=2,FirstName="Vikash",LastName="Nayak",Salary=45000},
                new GoldEmployee(){Id=3,FirstName="arjun",LastName="yadav",Salary=430000},
            };
            return emps;
        }

        public HttpResponseMessage Get()
        {
            try
            {
                var Employees = Load();

                if (Employees != null)
                {
                    return Request.CreateResponse(HttpStatusCode.OK, Employees);
                }
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No result found");
            }
            catch
            {
                return new HttpResponseMessage(HttpStatusCode.InternalServerError);
            }

        }

        public HttpResponseMessage Get(int id)
        {
            try
            {
                GoldEmployee emp = Load().Where(x => x.Id == id).SingleOrDefault();
                if (emp != null)
                {
                    return Request.CreateResponse(HttpStatusCode.OK, emp);
                }
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Employee with id " + id + " Not found");

            }
            catch
            {
                return new HttpResponseMessage(HttpStatusCode.InternalServerError);
            }
        }
      
      }
  }

Also, our old controller's name has been changed from EmployeeService to SilverEmployeeService like below.


   namespace MyFirstAPIProject.Controllers
    {
    public class SilverEmployeeServiceController : ApiController
    {
        public List<Employee> Load()
        {

            List<Employee> Employees = new List<Employee>(){
                new Employee(){Id=1,Name="Sachin",Salary=600000},
                 new Employee(){Id=2,Name="Arjun",Salary=350000},
                  new Employee(){Id=3,Name="Vikash",Salary=500000},
                   new Employee(){Id=4,Name="Abhijit",Salary=45000},
            };
            return Employees;
        }

      public HttpResponseMessage Get()
        {
            try
            {
                var Employees = Load();
               
                if (Employees != null)
                {
                    return Request.CreateResponse(HttpStatusCode.OK, Employees);
                }
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No result found");
            }
            catch
            {
                return new HttpResponseMessage(HttpStatusCode.InternalServerError);
            }

        }
        public HttpResponseMessage Get(int id)
        {
             try
             {
                 Employee emp = Load().Where(x => x.Id == id).SingleOrDefault();
                 if (emp != null)
                 {
                     return Request.CreateResponse(HttpStatusCode.OK, emp);
                 }
                 return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Employee with id " + id + " Not found");

             }
             catch
             {
                 return new HttpResponseMessage(HttpStatusCode.InternalServerError);
             }
        }
       
     }
    }

Thus, now we have two versions of the Service
  • The SilverEmployeeService signify the version 1 of EmployeeService.
  • The GoldEmployeeService signifies version 2 of EmployeeService.

But, All these changes are only from the developer's point of view. Our client knows nothing about the actual implementation, they just know that they are consuming EmployeeService.

Now, let's come to the real topic versioning using URI.

It means clients will consume the service by specifying the version number in the URL as routed data, and its developer's duty that based on the version number how they tell the web API to select appropriate controller.

This can be achieved in multiple ways, but the easiest way is through routing. And we know Routes can be defined in two ways.
  1. Convention-based routing
  2. Attribute routing.

Approach 1 Convention-based routing

Go to webApiConfig.cs under App_Start folder of the application and define routes like below.


     public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
           
            config.Routes.MapHttpRoute(
               name: "Version1",
               routeTemplate: "api/v1/EmployeeService/{id}",
               defaults: new { id = RouteParameter.Optional,controller="SilverEmployeeService" }
           );
            config.Routes.MapHttpRoute(
              name: "Version1",
              routeTemplate: "api/v2/EmployeeService/{id}",
              defaults: new { id = RouteParameter.Optional, controller = "GoldEmployeeService" }
          );



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

As you can see for clients it's still EmployeeService but available in two versions.

Attribute routing
Versioning

Approach 2 Attribute Routing

For attribute routing we use Route attribute on the Methods to define various routes. Like shown below.

Attribute routing
Attribute routing

Note:- while using attribute routing Comment the Convention-Based routing that define the versioning ,also enable the attribute routing in WebApiConfig.cs as shown below.

Attribute routing
Enable Attribute routing

Now run the application and fire get request with different URLs and test whether versioning using URI is a success or not.

Attribute routing
Versioning using URI

As you can see ,with the URL http://localhost:1069/api/v2/EmployeeService ,we successfully gets the list of GoldEmployee.

Let's summarize the Web API versioning using URI ,with figure.

Summarize URI Versioning
Versioning using URI (Summary)