// 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;
}
}
}