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

Custom Media Type Routing application/vnd.demo.students.v1+json Vendor Resource Version Format CustomControllerSelector Parses version from Accept header Controller Routing v1 ? StudentsV1 v2 ? StudentsV2

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+json routes to StudentsV1Controller

  • application/vnd.demo.students.v2+json routes to StudentsV2Controller

  • The same logic applies for XML format with +xml suffix

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.

Updated on: 2026-03-17T07:04:36+05:30

343 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements