// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Web.Http.Controllers; using System.Web.Http.Properties; using System.Web.Http.Routing; namespace System.Web.Http.Dispatcher { /// /// Default instance for choosing a given a /// A different implementation can be registered via the . /// public class DefaultHttpControllerSelector : IHttpControllerSelector { public static readonly string ControllerSuffix = "Controller"; private const string ControllerKey = "controller"; private readonly HttpConfiguration _configuration; private readonly HttpControllerTypeCache _controllerTypeCache; private readonly Lazy> _controllerInfoCache; /// /// Initializes a new instance of the class. /// /// The configuration. public DefaultHttpControllerSelector(HttpConfiguration configuration) { if (configuration == null) { throw Error.ArgumentNull("configuration"); } _controllerInfoCache = new Lazy>(InitializeControllerInfoCache); _configuration = configuration; _controllerTypeCache = new HttpControllerTypeCache(_configuration); } [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing of response instance.")] public virtual HttpControllerDescriptor SelectController(HttpRequestMessage request) { if (request == null) { throw Error.ArgumentNull("request"); } string controllerName = GetControllerName(request); if (String.IsNullOrEmpty(controllerName)) { throw new HttpResponseException(request.CreateResponse(HttpStatusCode.NotFound)); } HttpControllerDescriptor controllerDescriptor; if (_controllerInfoCache.Value.TryGetValue(controllerName, out controllerDescriptor)) { return controllerDescriptor; } ICollection matchingTypes = _controllerTypeCache.GetControllerTypes(controllerName); // ControllerInfoCache is already initialized. Contract.Assert(matchingTypes.Count != 1); if (matchingTypes.Count == 0) { // no matching types throw new HttpResponseException(request.CreateResponse( HttpStatusCode.NotFound, Error.Format(SRResources.DefaultControllerFactory_ControllerNameNotFound, controllerName))); } else { // multiple matching types throw new HttpResponseException(request.CreateResponse( HttpStatusCode.InternalServerError, CreateAmbiguousControllerExceptionMessage(request.GetRouteData().Route, controllerName, matchingTypes))); } } public virtual IDictionary GetControllerMapping() { return _controllerInfoCache.Value.ToDictionary(c => c.Key, c => c.Value, StringComparer.OrdinalIgnoreCase); } public virtual string GetControllerName(HttpRequestMessage request) { if (request == null) { throw Error.ArgumentNull("request"); } IHttpRouteData routeData = request.GetRouteData(); if (routeData == null) { return null; } // Look up controller in route data string controllerName = null; routeData.Values.TryGetValue(ControllerKey, out controllerName); return controllerName; } private static string CreateAmbiguousControllerExceptionMessage(IHttpRoute route, string controllerName, ICollection matchingTypes) { Contract.Assert(route != null); Contract.Assert(controllerName != null); Contract.Assert(matchingTypes != null); // Generate an exception containing all the controller types StringBuilder typeList = new StringBuilder(); foreach (Type matchedType in matchingTypes) { typeList.AppendLine(); typeList.Append(matchedType.FullName); } return Error.Format(SRResources.DefaultControllerFactory_ControllerNameAmbiguous_WithRouteTemplate, controllerName, route.RouteTemplate, typeList); } private ConcurrentDictionary InitializeControllerInfoCache() { var result = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); var duplicateControllers = new HashSet(); Dictionary> controllerTypeGroups = _controllerTypeCache.Cache; foreach (KeyValuePair> controllerTypeGroup in controllerTypeGroups) { string controllerName = controllerTypeGroup.Key; foreach (IGrouping controllerTypesGroupedByNs in controllerTypeGroup.Value) { foreach (Type controllerType in controllerTypesGroupedByNs) { if (result.Keys.Contains(controllerName)) { duplicateControllers.Add(controllerName); break; } else { result.TryAdd(controllerName, new HttpControllerDescriptor(_configuration, controllerName, controllerType)); } } } } foreach (string duplicateController in duplicateControllers) { HttpControllerDescriptor descriptor; result.TryRemove(duplicateController, out descriptor); } return result; } } }