Skip to content

Instantly share code, notes, and snippets.

@abhayachauhan
Created September 30, 2015 23:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abhayachauhan/62851b9ce786a76e0a87 to your computer and use it in GitHub Desktop.
Save abhayachauhan/62851b9ce786a76e0a87 to your computer and use it in GitHub Desktop.
Versioning Controller Selector
public class MustBeVersion : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
var val = values[parameterName] as string;
if (string.IsNullOrEmpty(val)) return false;
int versionNumber;
return (val.Length >= 2 && val.ToLower()[0] == 'v' && int.TryParse(val[1].ToString(), out versionNumber));
}
}
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using System.Web.Http.Routing;
namespace API
{
public class VersionedControllerSelector : IHttpControllerSelector
{
private const string NamespaceKey = "version";
private const string ControllerKey = "controller";
private readonly HttpConfiguration _configuration;
private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controllers;
private readonly DefaultHttpControllerSelector _defaultHttpControllerSelector;
private readonly HashSet<string> _duplicates;
public VersionedControllerSelector(HttpConfiguration config)
{
_configuration = config;
_duplicates = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
_controllers = new Lazy<Dictionary<string, HttpControllerDescriptor>>(InitializeControllerDictionary);
}
public HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null) throw new HttpResponseException(HttpStatusCode.NotFound);
// Get the namespace and controller variables from the route data.
var namespaceName = GetNamespace(request);
if (string.IsNullOrEmpty(namespaceName)) throw new HttpResponseException(HttpStatusCode.NotFound);
var controllerName = GetRouteVariable<string>(routeData, ControllerKey);
if (string.IsNullOrEmpty(controllerName)) throw new HttpResponseException(HttpStatusCode.NotFound);
var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", namespaceName, controllerName);
HttpControllerDescriptor controllerDescriptor;
if (_controllers.Value.TryGetValue(key, out controllerDescriptor)) return controllerDescriptor;
if (_duplicates.Contains(key))
{
throw new HttpResponseException(
request.CreateErrorResponse(HttpStatusCode.InternalServerError,
"Multiple controllers were found that match this request."));
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
private string GetNamespace(HttpRequestMessage request)
{
var nameSpace = GetVersionFromUrl(request);
if (!string.IsNullOrEmpty(nameSpace)) return nameSpace;
nameSpace = GetVersionFromHeader(request.Headers.Accept);
if (string.IsNullOrEmpty(nameSpace))
{
throw new HttpResponseException(
request.CreateErrorResponse(HttpStatusCode.InternalServerError,
"No api version supplied with this request."));
}
return nameSpace;
}
private static string GetVersionFromUrl(HttpRequestMessage request)
{bool urlVersioningEnabled;
bool.TryParse(ConfigurationManager.AppSettings["UrlVersioningEnabled"], out urlVersioningEnabled);
if (!urlVersioningEnabled) return string.Empty;
return GetRouteVariable<string>(request.GetRouteData(), NamespaceKey);
}
public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
{
return _controllers.Value;
}
private string GetVersionFromHeader(IEnumerable<MediaTypeWithQualityHeaderValue> acceptHeader)
{
var version = string.Empty;
foreach (var headerValue in acceptHeader)
{
var contentType = headerValue.MediaType.Substring(headerValue.MediaType.IndexOf('/'));
var tokens = contentType.Split(new[] {'-', '+'});
if (tokens.Length > 1 && tokens[1].ToLower().StartsWith("v")) version = tokens[1];
if (version != string.Empty) return version.ToUpper();
}
return version;
}
private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
{
var dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
// Create a lookup table where key is "namespace.controller". The value of "namespace" is the last
// segment of the full namespace. For example:
// MyApplication.Controllers.V1.ProductsController => "V1.Products"
IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
foreach (Type t in controllerTypes)
{
string[] segments = t.Namespace.Split(Type.Delimiter);
// For the dictionary key, strip "Controller" from the end of the type name.
// This matches the behavior of DefaultHttpControllerSelector.
string controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", segments[segments.Length - 1], controllerName);
// Check for duplicate keys.
if (dictionary.ContainsKey(key)) _duplicates.Add(key);
else dictionary[key] = new HttpControllerDescriptor(_configuration, t.Name, t);
}
// Remove any duplicates from the dictionary, because these create ambiguous matches.
// For example, "Foo.V1.ProductsController" and "Bar.V1.ProductsController" both map to "v1.products".
foreach (string s in _duplicates) dictionary.Remove(s);
return dictionary;
}
// Get a value from the route data, if present.
private static T GetRouteVariable<T>(IHttpRouteData routeData, string name)
{
object result = null;
if (routeData.Values.TryGetValue(name, out result)) return (T) result;
return default(T);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment