Created
September 30, 2015 23:53
-
-
Save abhayachauhan/62851b9ce786a76e0a87 to your computer and use it in GitHub Desktop.
Versioning Controller Selector
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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