Elegant API Versioning In ASP.NET Core

In this tutorial, we are going to learn how to develop simple Asp Net Core 3.1 Web API and API Versioning techniques such as URL-based, header-based, and Query string-based versioning.
 

What Is Web API Versioning? And Why Do We Need It?

 
Assume that we have an API available in the production box and a few clients are already consuming it. Post-production deployment, we should be very careful with any new changes. These changes should not impact the existing customer applications that are already consuming our API. It is not a good practice to advise clients to change the API calls in each of their applications where our API has been integrated. In order to address such issues, API versioning came into the picture.
 
API versions ensure that the integrity and availability of data for our existing client applications.
 
For the implementation, I will be using,
  • Microsoft Visual Studio Enterprise 2019
  • Version 16.8.4
  • ASP.NET Core 3.1
  • Postman for testing the API
P.S
Visual Studio 2019 Community Edition can be used.
 
To Begin with,
 
Step 1
 
Create a simple ASP.NET Core 3.1 API Project.
 
Step 2
 
Add a model ProductResponse.cs
  1. namespace AspNetCoreVersioning.Contracts {  
  2.     public class ProductResponse {  
  3.         public Guid Id {  
  4.             get;  
  5.             set;  
  6.         }  
  7.         public string Name {  
  8.             get;  
  9.             set;  
  10.         }  
  11.     }  
  12. }  
Step 3
 
Add a Controller ProductsController.cs
  1. namespace AspNetCoreVersioning.Controllers {  
  2.     [ApiController]  
  3.     [Route("api/products")]  
  4.     public class ProductsController: ControllerBase {  
  5.         [HttpGet("{productId}")]  
  6.         public IActionResult GetProduct([FromRoute] Guid productId) {  
  7.             var product = new ProductResponse {  
  8.                 Id = productId,  
  9.                     Name = "Sanitizer"  
  10.             };  
  11.             return Ok(product);  
  12.         }  
  13.     }  
  14. }  
Let us test this API, According to your environment, the port number will vary.
 
https://localhost:12345/api/products/00000000-0000-0000-0000-000000000000
 
Result
  1. {"id":"00000000-0000-0000-0000-000000000000","name":"Sanitizer"}   
We have received the expected result.
 
Assume that we need to make a breaking change on our API, but we don’t want to break anything in our existing API which our customers are already using. How can we achieve this? Let us go and resolve a NuGet package Microsoft.AspNetCore.Mvc.Versioning.
 
Use the below command from the package manager console,
 
Install-Package Microsoft.AspNetCore.Mvc.Versioning
 
Once the package installation complete, let us configure the API to support versioning. This package basically has everything that we need.
Without any delay, let us go to class Startup.cs, where we are going to update the method ConfigurationServices as below
  1. public void ConfigureServices(IServiceCollection services) {  
  2.     services.AddControllers();  
  3.     services.AddApiVersioning();  
  4. }  
By adding these services.AddApiVersioning() method, we have some sort of versioning. However, we haven’t really told ASP.NETCore how we versioning the API. Without making any additional changes, let us run the API, we will get the below error message
  1. {"error":{"code":"ApiVersionUnspecified","message":"An API version is required, but was not specified.","innerError":null}}   
This is because we haven’t configured our API versioning to a default version. For us the first version apparently V1 or V1.0. The default way of this works in ASP.NET Core is to have the “API-version” query string parameter as below.
 
https://localhost:12345/api/products/00000000-0000-0000-0000-000000000000?api-version=1
 
Result will as below,
  1. {"id":"00000000-0000-0000-0000-000000000000","name":"Sanitizer"}   
This would work fine. However, this is not ideal because if we make this “api-version” query string is mandatory, many of our consumers’ code break. What we want to do instead is we want to enable the API versioning default to a version. How we can achieve this? Let us to the Startup.cs class again and make the below changes.
  1. public void ConfigureServices(IServiceCollection services) {  
  2.     services.AddControllers();  
  3.     services.AddApiVersioning(options => {  
  4.         options.AssumeDefaultVersionWhenUnspecified = true;  
  5.     });  
  6. }  
Let us execute the API again,
 
https://localhost:12345/api/products/00000000-0000-0000-0000-000000000000
  1. Result: {"id":"00000000-0000-0000-0000-000000000000","name":"Sanitizer"}   
Assume that, if we want to have a different version instead of V1.0 or V1. How can we achieve this?
 
First, we will make the below changes in Startup.cs class as below,
  1. public void ConfigureServices(IServiceCollection services) {  
  2.     services.AddControllers();  
  3.     services.AddApiVersioning(options => {  
  4.         options.AssumeDefaultVersionWhenUnspecified = true;  
  5.         options.DefaultApiVersion = ApiVersion.Default;  
  6.     });  
  7. }  
Then make the below two changes.
 
Step 1
 
Add the below models in ProductResponse.cs Class,
  1. namespace AspNetCoreVersioning.Contracts {  
  2.     public class ProductResponseV1 {  
  3.         public Guid Id {  
  4.             get;  
  5.             set;  
  6.         }  
  7.         public string Name {  
  8.             get;  
  9.             set;  
  10.         }  
  11.     }  
  12.     public class ProductResponseV2 {  
  13.         public Guid Id {  
  14.             get;  
  15.             set;  
  16.         }  
  17.         public string ProductName {  
  18.             get;  
  19.             set;  
  20.         }  
  21.     }  
  22. }  
Step 2
 
Make the below changes into ProductsController.cs Class
  1. namespace AspNetCoreVersioning.Controllers {  
  2.         [ApiController]  
  3.         [Route("api/products")]  
  4.         public class ProductsController: ControllerBase {  
  5.             [HttpGet("{productId}")]  
  6.             public IActionResult GetProductV1([FromRoute] Guid productId) {  
  7.                     var product = new ProductResponseV1 {  
  8.                         Id = productId,  
  9.                             Name = "Sanitizer"  
  10.                     };  
  11.                     return Ok(product);  
  12.                 }  
  13.                 [HttpGet("{productId}")]  
  14.             public IActionResult GetProductV2([FromRoute] Guid productId) {  
  15.                 var product = new ProductResponseV2 {  
  16.                     Id = productId,  
  17.                         ProductName = "Sanitizer"  
  18.                 };  
  19.                 return Ok(product);  
  20.             }  
  21.         }  
Now Run the API. You will get the below Exception.
 
An unhandled exception occurred while processing the request.
 
AmbiguousMatchException: The request matched multiple endpoints. Matches,
  1. AspNetCoreVersioning.Controllers.ProductsController.GetProductV2 (AspNetCoreVersioning)  
  2. AspNetCoreVersioning.Controllers.ProductsController.GetProductV1 (AspNetCoreVersioning)  
  3. Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ReportAmbiguity(CandidateState[] candidateState)  
In order to resolve this error message, we need to make some configuration changes at ProductsController.cs Class as below,
  1. namespace AspNetCoreVersioning.Controllers {  
  2.     [ApiController]  
  3.     [Route("api/products")]  
  4.     [ApiVersion("1.0")]  
  5.     [ApiVersion("2.0")]  
  6.     public class ProductsController: ControllerBase {  
  7.         [HttpGet("{productId}")]  
  8.         public IActionResult GetProductV1([FromRoute] Guid productId) {  
  9.                 var product = new ProductResponseV1 {  
  10.                     Id = productId,  
  11.                         Name = "Sanitizer"  
  12.                 };  
  13.                 return Ok(product);  
  14.             }  
  15.             [HttpGet("{productId}")]  
  16.             [MapToApiVersion("2.0")]  
  17.         public IActionResult GetProductV2([FromRoute] Guid productId) {  
  18.             var product = new ProductResponseV2 {  
  19.                 Id = productId,  
  20.                     ProductName = "Sanitizer"  
  21.             };  
  22.             return Ok(product);  
  23.         }  
  24.     }  
  25. }  
Now Run the API as below,
 
https://localhost:12345/api/products/00000000-0000-0000-0000-000000000000?api-version=1
  1. Result: {"id":"00000000-0000-0000-0000-000000000000","name":"Sanitizer"}   
https://localhost:12345/api/products/00000000-0000-0000-0000-000000000000?api-version=2
  1. Result: {"id":"00000000-0000-0000-0000-000000000000","productName":"Sanitizer"}   
I will be covering URL based and Header based versioning in the upcoming sessions. Thanks for reading my article. I appreciate your feedback in the comment section below.