Coding Time Travel: Exploring API Versioning in C#
Hey techies, I am happy to meet you all up with another interesting and important topic.
Without further delay, let’s get into the crucial aspect in C# which is nothing but API Versioning.
API Versioning:
API — Application Programming Interface
In laymen terms, API is nothing but a set of protocols, rules that allows two different software applications communicate with each other
Versioning
→It’s a technique used to manage changes and updates in an API(Application Programming Interface) without breaking the functionality for existing clients of the API.
→It enables multiple versions of the same API, ensuring that clients can continue to interact with the API even as it evolves.
Why the API Versioning is important?
Backward Compatibility:
API versioning ensures that existing clients don’t experience disruptions when new features or changes are introduced.
Granular Control:
It allows developers to incrementally release the changes, providing flexibility in managing updates in existing features.
Client Adaptation:
Clients can choose which API version to use, allowing them to adopt new features at their own pace
Types of API Versioning:
→API versioning in C# involves managing different versions of your application’s API to accommodate changes and updates over time while minimizing disruption to existing clients.
→Here, I will provide an overview of API versioning types, examples, advantages, disadvantages, and when and where to use each type
URI VERSIONING:
URI — Uniform Resource Identifierhttps://api.example.com/v1/resource
→When a client sends a request to this URI, the server examines the version specified in the URI (v1
in this case). The server's routing system then directs the request to the appropriate controller or logic associated with that version.
→This allows you to maintain separate implementations for different versions
using Microsoft.AspNetCore.Mvc;
namespace MyApi.Controllers.V1
{
[Route("v1/[controller]")]
[ApiController]
public class MyResourceController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("This is version 1 of the API.");
}
}
}
using Microsoft.AspNetCore.Mvc;
namespace MyApi.Controllers.V2
{
[Route("v2/[controller]")]
[ApiController]
public class MyResourceController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("This is version 2 of the API.");
}
}
}
QUERY PARAMETER VERSIONING:https://api.example.com/resource?version=v1
The client includes the version information as a query parameter (version=v1
) in the request. The server's routing logic indicates the query parameter and routes the request to the corresponding version's controller or business logic.
using Microsoft.AspNetCore.Mvc;
namespace MyApi.Controllers
{
[Route("[controller]")]
[ApiController]
public class MyResourceController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
// Get the version parameter from the query string
string version = HttpContext.Request.Query["version"];
// Checking the version parameter and returning the result
if (version == "v1")
{
return Ok("This is version 1 of the API.");
}
else if (version == "v2")
{
return Ok("This is version 2 of the API.");
}
else
{
return BadRequest("Not supported API version.");
}
}
}
}
In this example, the API version is specified as a query parameter in the request URL. For instance, to request version 1 of the API, you would make a GET request like mentioned below,
https://api.example.com/myresource?version=v1
HEADER VERSIONING:X-API-Version: v1
The client sets a custom HTTP header (X-API-Version: v1
) in the request. The server inspects this header to determine the version, then routes the request to the appropriate version's controller or logic.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
namespace MyApi.Controllers
{
[Route("[controller]")]
[ApiController]
public class MyResourceController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
// Get the custom X-API-{Version} header
var apiVersion = HttpContext.Request.Headers["X-API-Version"];
if (apiVersion == "v1")
{
return Ok("This is version 1 of the API.");
}
else if (apiVersion == "v2")
{
return Ok("This is version 2 of the API.");
}
else
{
return BadRequest("Not supported API version.");
}
}
}
}
MEDIA TYPE VERSIONING(Accept Header):Accept: application/vnd.example.v1+json
The client specifies the desired version in the Accept
header of the request. The server examines the media type and version information, then routes the request accordingly.
using Microsoft.AspNetCore.Mvc;
namespace MyApi.Controllers
{
[Route("[controller]")]
[ApiController]
public class MyResourceController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
// Get the "Accept" header from the request
string acceptHeader = HttpContext.Request.Headers["Accept"];
// Checking the Accept header for the requested version
if (acceptHeader.Contains("application/vnd.example.v1+json"))
{
return Ok("This is version 1 of the API.");
}
else if (acceptHeader.Contains("application/vnd.example.v2+json"))
{
return Ok("This is version 2 of the API.");
}
else
{
return BadRequest("Not supported API version.");
}
}
}
}
In this example, the API version is specified as a custom media type within the “Accept” header of the HTTP request. For example, to request version 1 of the API, you would make an HTTP GET request with the following “Accept” header,
Accept: application/vnd.example.v1+json
MEDIA TYPE VERSIONING(Custom Header):
→ Media Type Versioning with a custom header is a method of API versioning where you include the version information as part of a custom header in the HTTP request.
→ This allows you to specify both the desired content format and the API version in a single request
// Version 1 Controller
namespace MyApi.V1.Controllers
{
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/sample")]
public class SampleController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok(new { Message = "This is version 1 of the API." });
}
}
}
// Version 2 Controller
namespace MyApi.V2.Controllers
{
[ApiController]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/sample")]
public class SampleController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok(new { Message = "This is version 2 of the API." });
}
}
}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
using (var client = new HttpClient())
{
// To Access Version 2.0 API
client.DefaultRequestHeaders.Add("X-Custom-Version", "2.0"); // Specify the desired version
var response = await client.GetAsync("https://localhost:5001/api/v2/sample");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
}
}
In this example, we add the “X-Custom-Version” header with a value of “2.0” to indicate that we want to access version 2 of the API
NAMESPACE VERSIONING:
In the C# codebase, you organize different versions of the API logic into separate namespaces. For example, MyApi.V1
might contain controllers for version 1, while MyApi.V2
holds controllers for version 2. The server uses the appropriate namespace to determine which logic to execute based on the version provided.
namespace MyApi.V1.Controllers
{
// Version 1: Namespace: MyApi.V1
[Route("v1/[controller]")]
[ApiController]
public class MyResourceController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("This is version 1 of the API.");
}
}
}
namespace MyApi.V2.Controllers
{
// Version 1: Namespace: MyApi.V2
[Route("v2/[controller]")]
[ApiController]
public class MyResourceController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("This is version 2 of the API.");
}
}
}
In this example, the MyResourceController
for version 1 is placed in the MyApi.V1.Controllers
namespace, and the MyResourceController
for version 2 is placed in the MyApi.V2.Controllers
namespace. This separation allows you to maintain distinct versions of the controller logic within your codebase.
ROUTING VERSIONING:
You can configure your server’s routing system to direct requests with different versions to different controller actions. For example, using attributes like [Route("v1/resource")]
and [Route("v2/resource")]
in ASP.NET Core, the routing system ensures that requests with different version identifiers land in distinct controller methods.
using Microsoft.AspNetCore.Mvc;
namespace MyApi.Controllers
{
[ApiController]
public class MyResourceController : ControllerBase
{
[HttpGet]
[Route("v1/myresource")]
public IActionResult GetV1()
{
return Ok("This is version 1 of the API.");
}
[HttpGet]
[Route("v2/myresource")]
public IActionResult GetV2()
{
return Ok("This is version 2 of the API.");
}
}
}
In this example, the MyResourceController
class contains two action methods, GetV1
and GetV2
, each with its own route attribute specifying the version in the URI. Requests to /v1/myresource
will be handled by the GetV1
method, and requests to /v2/myresource
will be handled by the GetV2
method.
Best Practices for API Versioning:
→ Start with a Stable API: Begin with a well-designed and stable API.
→ Document Versions: Clearly document how to specify API versions.
→ Graceful Deprecation: Announce the deprecation of old versions and provide migration guidance.
→ Consistent Naming: Maintain a consistent naming convention for API versions.
→ Testing and Monitoring: Continuously test and monitor API versions for performance and reliability
Finally, to sum it up, API versioning is crucial for managing API evolution, ensuring a smooth experience for both existing and new clients as your API continues to develop and improve.
Hope this article helps to understand the API versioning and their types and when and where to use this according to business use cases.
I would like to bring this blog to closure with the famous quote,
“In the world of technology, change is the only constant.”
Stay tuned for more interesting articles soon. Can’t wait to catch up with another exiting topic soon.
Be Happy!! Stay Healthy!!
Happy Coding!!