Article Categories
- All Categories
-
Data Structure
-
Networking
-
RDBMS
-
Operating System
-
Java
-
MS Excel
-
iOS
-
HTML
-
CSS
-
Android
-
Python
-
C Programming
-
C++
-
C#
-
MongoDB
-
MySQL
-
Javascript
-
PHP
-
Economics & Finance
How to do versioning with custom media type in C# ASP.NET WebAPI?
API versioning through custom media types allows clients to specify which version of an API they want to use by including version information in the Accept header. This approach uses vendor-specific media types to route requests to the appropriate controller version based on the requested content type.
Custom media types follow a pattern like application/vnd.company.resource.version+format, where the version is embedded within the media type identifier itself.
Media Type Versioning Pattern
The following media types route to different controller versions −
application/vnd.demo.students.v1+json ? StudentsV1Controller application/vnd.demo.students.v2+json ? StudentsV2Controller
Creating the Custom Controller Selector
The CustomControllerSelector extracts version information from the Accept header using regex patterns and routes requests to the appropriate versioned controller −
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
namespace WebAPI.Custom
{
public class CustomControllerSelector : DefaultHttpControllerSelector
{
private HttpConfiguration _config;
public CustomControllerSelector(HttpConfiguration config) : base(config)
{
_config = config;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var controllers = GetControllerMapping();
var routeData = request.GetRouteData();
var controllerName = routeData.Values["controller"].ToString();
string versionNumber = "";
string regex = @"application\/vnd\.demo\.([a-z]+)\.v(?<version>[0-9]+)\+([a-z]+)";
var acceptHeader = request.Headers.Accept
.Where(a => Regex.IsMatch(a.MediaType, regex, RegexOptions.IgnoreCase));
if (acceptHeader.Any())
{
var match = Regex.Match(acceptHeader.First().MediaType, regex, RegexOptions.IgnoreCase);
versionNumber = match.Groups["version"].Value;
}
HttpControllerDescriptor controllerDescriptor;
if (versionNumber == "1")
{
controllerName = string.Concat(controllerName, "V1");
}
else if (versionNumber == "2")
{
controllerName = string.Concat(controllerName, "V2");
}
if (controllers.TryGetValue(controllerName, out controllerDescriptor))
{
return controllerDescriptor;
}
return null;
}
}
}
Configuring WebAPI
Register the custom controller selector in the WebAPI configuration −
using System.Net.Http.Headers;
using System.Web.Http;
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector(config));
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Creating Versioned Controllers
Version 1 Controller
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace DemoWebApplication.Controllers
{
public class StudentV1Controller : ApiController
{
List<StudentV1> students = new List<StudentV1>
{
new StudentV1 { Id = 1, Name = "Mark" },
new StudentV1 { Id = 2, Name = "John" }
};
public IEnumerable<StudentV1> Get()
{
return students;
}
public StudentV1 Get(int id)
{
var studentForId = students.FirstOrDefault(x => x.Id == id);
return studentForId;
}
}
}
Version 2 Controller
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace DemoWebApplication.Controllers
{
public class StudentV2Controller : ApiController
{
List<StudentV2> students = new List<StudentV2>
{
new StudentV2 { Id = 1, FirstName = "Roger", LastName = "Federer" },
new StudentV2 { Id = 2, FirstName = "Tom", LastName = "Bruce" }
};
public IEnumerable<StudentV2> Get()
{
return students;
}
public StudentV2 Get(int id)
{
var studentForId = students.FirstOrDefault(x => x.Id == id);
return studentForId;
}
}
}
Supporting XML Format
To support XML responses with custom media types, add the media types to the XML formatter configuration −
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector(config));
config.Formatters.XmlFormatter.SupportedMediaTypes
.Add(new MediaTypeHeaderValue("application/vnd.demo.student.v1+xml"));
config.Formatters.XmlFormatter.SupportedMediaTypes
.Add(new MediaTypeHeaderValue("application/vnd.demo.student.v2+xml"));
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
How It Works
When a client makes a request with a custom media type in the Accept header, the CustomControllerSelector uses regex to extract the version number and appends it to the controller name. For example:
application/vnd.demo.students.v1+jsonroutes toStudentsV1Controllerapplication/vnd.demo.students.v2+jsonroutes toStudentsV2ControllerThe same logic applies for XML format with
+xmlsuffix
Conclusion
Custom media type versioning provides a clean way to version APIs by embedding version information directly in the content type. This approach allows different versions of controllers to coexist while maintaining backward compatibility, with the custom controller selector automatically routing requests based on the Accept header.
