// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using System.Web.Http.Internal;
using System.Web.Http.ModelBinding.Binders;
using System.Web.Http.Routing;
using System.Web.Http.Services;
namespace System.Web.Http.Description
{
///
/// Explores the URI space of the service based on routes, controllers and actions available in the system.
///
public class ApiExplorer : IApiExplorer
{
private Lazy> _apiDescriptions;
private readonly HttpConfiguration _config;
private static readonly Regex _actionVariableRegex = new Regex(String.Format(CultureInfo.CurrentCulture, "{{{0}}}", RouteValueKeys.Action), RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private static readonly Regex _controllerVariableRegex = new Regex(String.Format(CultureInfo.CurrentCulture, "{{{0}}}", RouteValueKeys.Controller), RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
///
/// Initializes a new instance of the class.
///
/// The configuration.
public ApiExplorer(HttpConfiguration configuration)
{
_config = configuration;
_apiDescriptions = new Lazy>(InitializeApiDescriptions);
}
///
/// Gets the API descriptions. The descriptions are initialized on the first access.
///
public Collection ApiDescriptions
{
get
{
return _apiDescriptions.Value;
}
}
///
/// Gets or sets the documentation provider. The provider will be responsible for documenting the API.
///
///
/// The documentation provider.
///
public IDocumentationProvider DocumentationProvider { get; set; }
///
/// Determines whether the controller should be considered for generation. Called when initializing the .
///
/// The controller variable value from the route.
/// The controller descriptor.
/// The route.
/// true if the controller should be considered for generation, false otherwise.
public virtual bool ShouldExploreController(string controllerVariableValue, HttpControllerDescriptor controllerDescriptor, IHttpRoute route)
{
if (controllerDescriptor == null)
{
throw Error.ArgumentNull("controllerDescriptor");
}
if (route == null)
{
throw Error.ArgumentNull("route");
}
ApiExplorerSettingsAttribute setting = controllerDescriptor.GetCustomAttributes().FirstOrDefault();
return (setting == null || !setting.IgnoreApi) &&
MatchRegexConstraint(route, RouteValueKeys.Controller, controllerVariableValue);
}
///
/// Determines whether the action should be considered for generation. Called when initializing the .
///
/// The action variable value from the route.
/// The action descriptor.
/// The route.
/// true if the action should be considered for generation, false otherwise.
public virtual bool ShouldExploreAction(string actionVariableValue, HttpActionDescriptor actionDescriptor, IHttpRoute route)
{
if (actionDescriptor == null)
{
throw Error.ArgumentNull("actionDescriptor");
}
if (route == null)
{
throw Error.ArgumentNull("route");
}
ApiExplorerSettingsAttribute setting = actionDescriptor.GetCustomAttributes().FirstOrDefault();
return (setting == null || !setting.IgnoreApi) &&
MatchRegexConstraint(route, RouteValueKeys.Action, actionVariableValue);
}
///
/// Gets a collection of HttpMethods supported by the action. Called when initializing the .
///
/// The route.
/// The action descriptor.
/// A collection of HttpMethods supported by the action.
public virtual Collection GetHttpMethodsSupportedByAction(IHttpRoute route, HttpActionDescriptor actionDescriptor)
{
if (route == null)
{
throw Error.ArgumentNull("route");
}
if (actionDescriptor == null)
{
throw Error.ArgumentNull("actionDescriptor");
}
IList supportedMethods = new List();
IList actionHttpMethods = actionDescriptor.SupportedHttpMethods;
HttpMethodConstraint httpMethodConstraint = route.Constraints.Values.FirstOrDefault(c => typeof(HttpMethodConstraint).IsAssignableFrom(c.GetType())) as HttpMethodConstraint;
if (httpMethodConstraint == null)
{
supportedMethods = actionHttpMethods;
}
else
{
supportedMethods = httpMethodConstraint.AllowedMethods.Intersect(actionHttpMethods).ToList();
}
return new Collection(supportedMethods);
}
private IEnumerable FlattenRoutes(IEnumerable routes)
{
foreach (IHttpRoute route in routes)
{
IEnumerable nested = route as IEnumerable;
if (nested != null)
{
foreach (var subRoute in FlattenRoutes(nested))
{
yield return subRoute;
}
}
else
{
yield return route;
}
}
}
private static HttpControllerDescriptor GetDirectRouteController(CandidateAction[] directRouteCandidates)
{
if (directRouteCandidates != null)
{
// Set the controller descriptor for the first action descriptor
HttpControllerDescriptor controllerDescriptor = directRouteCandidates[0].ActionDescriptor.ControllerDescriptor;
// Check that all other action descriptors share the same controller descriptor
for (int i = 1; i < directRouteCandidates.Length; i++)
{
if (directRouteCandidates[i].ActionDescriptor.ControllerDescriptor != controllerDescriptor)
{
// This can happen if a developer puts the same route template on different actions
// in different controllers.
return null;
}
}
return controllerDescriptor;
}
return null;
}
private Collection InitializeApiDescriptions()
{
Collection apiDescriptions = new Collection();
IHttpControllerSelector controllerSelector = _config.Services.GetHttpControllerSelector();
IDictionary controllerMappings = controllerSelector.GetControllerMapping();
if (controllerMappings != null)
{
ApiDescriptionComparer descriptionComparer = new ApiDescriptionComparer();
foreach (IHttpRoute route in FlattenRoutes(_config.Routes))
{
CandidateAction[] directRouteCandidates = route.GetDirectRouteCandidates();
HttpControllerDescriptor directRouteController = GetDirectRouteController(directRouteCandidates);
Collection descriptionsFromRoute =
(directRouteController != null && directRouteCandidates != null) ?
ExploreDirectRoute(directRouteController, directRouteCandidates, route) :
ExploreRouteControllers(controllerMappings, route);
// Remove ApiDescription that will lead to ambiguous action matching.
// E.g. a controller with Post() and PostComment(). When the route template is {controller}, it produces POST /controller and POST /controller.
descriptionsFromRoute = RemoveInvalidApiDescriptions(descriptionsFromRoute);
foreach (ApiDescription description in descriptionsFromRoute)
{
// Do not add the description if the previous route has a matching description with the same HTTP method and relative path.
// E.g. having two routes with the templates "api/Values/{id}" and "api/{controller}/{id}" can potentially produce the same
// relative path "api/Values/{id}" but only the first one matters.
if (!apiDescriptions.Contains(description, descriptionComparer))
{
apiDescriptions.Add(description);
}
}
}
}
return apiDescriptions;
}
private Collection ExploreDirectRoute(HttpControllerDescriptor controllerDescriptor, CandidateAction[] candidates, IHttpRoute route)
{
Collection descriptions = new Collection();
if (ShouldExploreController(controllerDescriptor.ControllerName, controllerDescriptor, route))
{
foreach (CandidateAction action in candidates)
{
HttpActionDescriptor actionDescriptor = action.ActionDescriptor;
string actionName = actionDescriptor.ActionName;
if (ShouldExploreAction(actionName, actionDescriptor, route))
{
string routeTemplate = route.RouteTemplate;
if (_actionVariableRegex.IsMatch(routeTemplate))
{
// expand {action} variable
routeTemplate = _actionVariableRegex.Replace(routeTemplate, actionName);
}
PopulateActionDescriptions(actionDescriptor, route, routeTemplate, descriptions);
}
}
}
return descriptions;
}
private Collection ExploreRouteControllers(IDictionary controllerMappings, IHttpRoute route)
{
Collection apiDescriptions = new Collection();
string routeTemplate = route.RouteTemplate;
string controllerVariableValue;
if (_controllerVariableRegex.IsMatch(routeTemplate))
{
// unbound controller variable, {controller}
foreach (KeyValuePair controllerMapping in controllerMappings)
{
controllerVariableValue = controllerMapping.Key;
HttpControllerDescriptor controllerDescriptor = controllerMapping.Value;
if (ShouldExploreController(controllerVariableValue, controllerDescriptor, route))
{
// expand {controller} variable
string expandedRouteTemplate = _controllerVariableRegex.Replace(routeTemplate, controllerVariableValue);
ExploreRouteActions(route, expandedRouteTemplate, controllerDescriptor, apiDescriptions);
}
}
}
else if (route.Defaults.TryGetValue(RouteValueKeys.Controller, out controllerVariableValue))
{
// bound controller variable, {controller = "controllerName"}
HttpControllerDescriptor controllerDescriptor;
if (controllerMappings.TryGetValue(controllerVariableValue, out controllerDescriptor) && ShouldExploreController(controllerVariableValue, controllerDescriptor, route))
{
ExploreRouteActions(route, routeTemplate, controllerDescriptor, apiDescriptions);
}
}
return apiDescriptions;
}
private void ExploreRouteActions(IHttpRoute route, string localPath, HttpControllerDescriptor controllerDescriptor, Collection apiDescriptions)
{
// exclude controllers that are marked with route attributes.
if (!controllerDescriptor.IsAttributeRouted())
{
ServicesContainer controllerServices = controllerDescriptor.Configuration.Services;
ILookup actionMappings = controllerServices.GetActionSelector().GetActionMapping(controllerDescriptor);
string actionVariableValue;
if (actionMappings != null)
{
if (_actionVariableRegex.IsMatch(localPath))
{
// unbound action variable, {action}
foreach (IGrouping actionMapping in actionMappings)
{
// expand {action} variable
actionVariableValue = actionMapping.Key;
string expandedLocalPath = _actionVariableRegex.Replace(localPath, actionVariableValue);
PopulateActionDescriptions(actionMapping, actionVariableValue, route, expandedLocalPath, apiDescriptions);
}
}
else if (route.Defaults.TryGetValue(RouteValueKeys.Action, out actionVariableValue))
{
// bound action variable, { action = "actionName" }
PopulateActionDescriptions(actionMappings[actionVariableValue], actionVariableValue, route, localPath, apiDescriptions);
}
else
{
// no {action} specified, e.g. {controller}/{id}
foreach (IGrouping actionMapping in actionMappings)
{
PopulateActionDescriptions(actionMapping, null, route, localPath, apiDescriptions);
}
}
}
}
}
private void PopulateActionDescriptions(IEnumerable actionDescriptors, string actionVariableValue, IHttpRoute route, string localPath, Collection apiDescriptions)
{
foreach (HttpActionDescriptor actionDescriptor in actionDescriptors)
{
if (ShouldExploreAction(actionVariableValue, actionDescriptor, route))
{
// exclude actions that are marked with route attributes except for the inherited actions.
if (!actionDescriptor.IsAttributeRouted())
{
PopulateActionDescriptions(actionDescriptor, route, localPath, apiDescriptions);
}
}
}
}
private void PopulateActionDescriptions(HttpActionDescriptor actionDescriptor, IHttpRoute route, string localPath, Collection apiDescriptions)
{
string apiDocumentation = GetApiDocumentation(actionDescriptor);
HttpParsedRoute parsedRoute = RouteParser.Parse(localPath);
// parameters
IList parameterDescriptions = CreateParameterDescriptions(actionDescriptor, parsedRoute, route.Defaults);
// expand all parameter variables
string finalPath;
if (!TryExpandUriParameters(route, parsedRoute, parameterDescriptions, out finalPath))
{
// the action cannot be reached due to parameter mismatch, e.g. routeTemplate = "/users/{name}" and GetUsers(id)
return;
}
// request formatters
ApiParameterDescription bodyParameter = parameterDescriptions.FirstOrDefault(description => description.Source == ApiParameterSource.FromBody);
IEnumerable supportedRequestBodyFormatters = bodyParameter != null ?
actionDescriptor.Configuration.Formatters.Where(f => f.CanReadType(bodyParameter.ParameterDescriptor.ParameterType)) :
Enumerable.Empty();
// response formatters
ResponseDescription responseDescription = CreateResponseDescription(actionDescriptor);
Type returnType = responseDescription.ResponseType ?? responseDescription.DeclaredType;
IEnumerable supportedResponseFormatters = (returnType != null && returnType != typeof(void)) ?
actionDescriptor.Configuration.Formatters.Where(f => f.CanWriteType(returnType)) :
Enumerable.Empty();
// Replacing the formatter tracers with formatters if tracers are present.
supportedRequestBodyFormatters = GetInnerFormatters(supportedRequestBodyFormatters);
supportedResponseFormatters = GetInnerFormatters(supportedResponseFormatters);
// get HttpMethods supported by an action. Usually there is one HttpMethod per action but we allow multiple of them per action as well.
IList supportedMethods = GetHttpMethodsSupportedByAction(route, actionDescriptor);
foreach (HttpMethod method in supportedMethods)
{
apiDescriptions.Add(new ApiDescription
{
Documentation = apiDocumentation,
HttpMethod = method,
RelativePath = finalPath,
ActionDescriptor = actionDescriptor,
Route = route,
SupportedResponseFormatters = new Collection(supportedResponseFormatters.ToList()),
SupportedRequestBodyFormatters = new Collection(supportedRequestBodyFormatters.ToList()),
ParameterDescriptions = new Collection(parameterDescriptions),
ResponseDescription = responseDescription
});
}
}
private ResponseDescription CreateResponseDescription(HttpActionDescriptor actionDescriptor)
{
Collection responseTypeAttribute = actionDescriptor.GetCustomAttributes();
Type responseType = responseTypeAttribute.Select(attribute => attribute.ResponseType).FirstOrDefault();
return new ResponseDescription
{
DeclaredType = actionDescriptor.ReturnType,
ResponseType = responseType,
Documentation = GetApiResponseDocumentation(actionDescriptor)
};
}
private static IEnumerable GetInnerFormatters(IEnumerable mediaTypeFormatters)
{
foreach (MediaTypeFormatter formatter in mediaTypeFormatters)
{
yield return Decorator.GetInner(formatter);
}
}
private static bool ShouldEmitPrefixes(ICollection parameterDescriptions)
{
// Determine if there are two or more complex objects from the Uri so TryExpandUriParameters needs to emit prefixes.
return parameterDescriptions.Count(parameter =>
parameter.Source == ApiParameterSource.FromUri &&
parameter.ParameterDescriptor != null &&
!TypeHelper.CanConvertFromString(parameter.ParameterDescriptor.ParameterType) &&
parameter.CanConvertPropertiesFromString()) > 1;
}
// Set as internal for the unit test.
internal static bool TryExpandUriParameters(IHttpRoute route, HttpParsedRoute parsedRoute, ICollection parameterDescriptions, out string expandedRouteTemplate)
{
Dictionary parameterValuesForRoute = new Dictionary(StringComparer.OrdinalIgnoreCase);
bool emitPrefixes = ShouldEmitPrefixes(parameterDescriptions);
string prefix = String.Empty;
foreach (ApiParameterDescription parameterDescription in parameterDescriptions)
{
if (parameterDescription.Source == ApiParameterSource.FromUri)
{
if (parameterDescription.ParameterDescriptor == null)
{
// Undeclared route parameter handling generates query string like
// "?name={name}"
AddPlaceholder(parameterValuesForRoute, parameterDescription.Name);
}
else if (TypeHelper.CanConvertFromString(parameterDescription.ParameterDescriptor.ParameterType))
{
// Simple type generates query string like
// "?name={name}"
AddPlaceholder(parameterValuesForRoute, parameterDescription.Name);
}
else if (IsBindableCollection(parameterDescription.ParameterDescriptor.ParameterType))
{
string parameterName = parameterDescription.ParameterDescriptor.ParameterName;
Type innerType =
GetCollectionElementType(parameterDescription.ParameterDescriptor.ParameterType);
PropertyInfo[] innerTypeProperties =
ApiParameterDescription.GetBindableProperties(innerType).ToArray();
if (innerTypeProperties.Any())
{
// Complex array and collection generate query string like
// "?name[0].foo={name[0].foo}&name[0].bar={name[0].bar}
// &name[1].foo={name[1].foo}&name[1].bar={name[1].bar}"
AddPlaceholderForProperties(parameterValuesForRoute,
innerTypeProperties,
parameterName + "[0].");
AddPlaceholderForProperties(parameterValuesForRoute,
innerTypeProperties,
parameterName + "[1].");
}
else
{
// Simple array and collection generate query string like
// "?name[0]={name[0]}&name[1]={name[1]}".
AddPlaceholder(parameterValuesForRoute, parameterName + "[0]");
AddPlaceholder(parameterValuesForRoute, parameterName + "[1]");
}
}
else if (IsBindableKeyValuePair(parameterDescription.ParameterDescriptor.ParameterType))
{
// KeyValuePair generates query string like
// "?key={key}&value={value}"
AddPlaceholder(parameterValuesForRoute, "key");
AddPlaceholder(parameterValuesForRoute, "value");
}
else if (IsBindableDictionry(parameterDescription.ParameterDescriptor.ParameterType))
{
// Dictionary generates query string like
// "?dict[0].key={dict[0].key}&dict[0].value={dict[0].value}
// &dict[1].key={dict[1].key}&dict[1].value={dict[1].value}"
string parameterName = parameterDescription.ParameterDescriptor.ParameterName;
AddPlaceholder(parameterValuesForRoute, parameterName + "[0].key");
AddPlaceholder(parameterValuesForRoute, parameterName + "[0].value");
AddPlaceholder(parameterValuesForRoute, parameterName + "[1].key");
AddPlaceholder(parameterValuesForRoute, parameterName + "[1].value");
}
else if (parameterDescription.CanConvertPropertiesFromString())
{
if (emitPrefixes)
{
prefix = parameterDescription.Name + ".";
}
// Inserting the individual properties of the object in the query string
// as all the complex object can not be converted from string, but all its
// individual properties can.
AddPlaceholderForProperties(parameterValuesForRoute,
parameterDescription.GetBindableProperties(),
prefix);
}
}
}
BoundRouteTemplate boundRouteTemplate = parsedRoute.Bind(null, parameterValuesForRoute, new HttpRouteValueDictionary(route.Defaults), new HttpRouteValueDictionary(route.Constraints));
if (boundRouteTemplate == null)
{
expandedRouteTemplate = null;
return false;
}
expandedRouteTemplate = Uri.UnescapeDataString(boundRouteTemplate.BoundTemplate);
return true;
}
private static Type GetCollectionElementType(Type collectionType)
{
Contract.Assert(!typeof(IDictionary).IsAssignableFrom(collectionType));
Type elementType = collectionType.GetElementType();
if (elementType == null)
{
elementType = CollectionModelBinderUtil
.GetGenericBinderTypeArgs(typeof(ICollection<>), collectionType)
.First();
}
return elementType;
}
private static void AddPlaceholderForProperties(Dictionary parameterValuesForRoute,
IEnumerable properties,
string prefix)
{
foreach (PropertyInfo property in properties)
{
string queryParameterName = prefix + property.Name;
AddPlaceholder(parameterValuesForRoute, queryParameterName);
}
}
private static bool IsBindableCollection(Type type)
{
Contract.Assert(type != null);
return type.IsArray || new CollectionModelBinderProvider().GetBinder(null, type) != null;
}
private static bool IsBindableDictionry(Type type)
{
Contract.Assert(type != null);
return new DictionaryModelBinderProvider().GetBinder(null, type) != null;
}
private static bool IsBindableKeyValuePair(Type type)
{
Contract.Assert(type != null);
return TypeHelper.GetTypeArgumentsIfMatch(type, typeof(KeyValuePair<,>)) != null;
}
private static void AddPlaceholder(Dictionary parameterValuesForRoute,
string queryParameterName)
{
if (!parameterValuesForRoute.ContainsKey(queryParameterName))
{
parameterValuesForRoute.Add(queryParameterName, "{" + queryParameterName + "}");
}
}
private IList CreateParameterDescriptions(HttpActionDescriptor actionDescriptor, HttpParsedRoute parsedRoute, IDictionary routeDefaults)
{
IList parameterDescriptions = new List();
HttpActionBinding actionBinding = GetActionBinding(actionDescriptor);
// try get parameter binding information if available
if (actionBinding != null)
{
HttpParameterBinding[] parameterBindings = actionBinding.ParameterBindings;
if (parameterBindings != null)
{
foreach (HttpParameterBinding parameter in parameterBindings)
{
parameterDescriptions.Add(CreateParameterDescriptionFromBinding(parameter));
}
}
}
else
{
Collection parameters = actionDescriptor.GetParameters();
if (parameters != null)
{
foreach (HttpParameterDescriptor parameter in parameters)
{
parameterDescriptions.Add(CreateParameterDescriptionFromDescriptor(parameter));
}
}
}
// Adding route parameters not declared on the action. We're doing this because route parameters may or
// may not be part of the action parameters and we want to have them in the description.
AddUndeclaredRouteParameters(parsedRoute, routeDefaults, parameterDescriptions);
return parameterDescriptions;
}
private static void AddUndeclaredRouteParameters(HttpParsedRoute parsedRoute, IDictionary routeDefaults, IList parameterDescriptions)
{
foreach (PathSegment path in parsedRoute.PathSegments)
{
PathContentSegment content = path as PathContentSegment;
if (content != null)
{
foreach (PathSubsegment subSegment in content.Subsegments)
{
PathParameterSubsegment parameter = subSegment as PathParameterSubsegment;
if (parameter != null)
{
object parameterValue;
string parameterName = parameter.ParameterName;
if (!parameterDescriptions.Any(p => String.Equals(p.Name, parameterName, StringComparison.OrdinalIgnoreCase)) &&
(!routeDefaults.TryGetValue(parameterName, out parameterValue) ||
parameterValue != RouteParameter.Optional))
{
parameterDescriptions.Add(
new ApiParameterDescription
{
Name = parameterName,
Source = ApiParameterSource.FromUri
});
}
}
}
}
}
}
private ApiParameterDescription CreateParameterDescriptionFromDescriptor(HttpParameterDescriptor parameter)
{
Contract.Assert(parameter != null);
return new ApiParameterDescription
{
ParameterDescriptor = parameter,
Name = parameter.Prefix ?? parameter.ParameterName,
Documentation = GetApiParameterDocumentation(parameter),
Source = ApiParameterSource.Unknown,
};
}
private ApiParameterDescription CreateParameterDescriptionFromBinding(HttpParameterBinding parameterBinding)
{
ApiParameterDescription parameterDescription = CreateParameterDescriptionFromDescriptor(parameterBinding.Descriptor);
if (parameterBinding.WillReadBody)
{
parameterDescription.Source = ApiParameterSource.FromBody;
}
else if (parameterBinding.WillReadUri())
{
parameterDescription.Source = ApiParameterSource.FromUri;
}
return parameterDescription;
}
private string GetApiDocumentation(HttpActionDescriptor actionDescriptor)
{
IDocumentationProvider documentationProvider = DocumentationProvider ?? actionDescriptor.Configuration.Services.GetDocumentationProvider();
if (documentationProvider != null)
{
return documentationProvider.GetDocumentation(actionDescriptor);
}
return null;
}
private string GetApiParameterDocumentation(HttpParameterDescriptor parameterDescriptor)
{
IDocumentationProvider documentationProvider = DocumentationProvider ?? parameterDescriptor.Configuration.Services.GetDocumentationProvider();
if (documentationProvider != null)
{
return documentationProvider.GetDocumentation(parameterDescriptor);
}
return null;
}
private string GetApiResponseDocumentation(HttpActionDescriptor actionDescriptor)
{
IDocumentationProvider documentationProvider = DocumentationProvider ?? actionDescriptor.Configuration.Services.GetDocumentationProvider();
if (documentationProvider != null)
{
return documentationProvider.GetResponseDocumentation(actionDescriptor);
}
return null;
}
// remove ApiDescription that will lead to ambiguous action matching.
private static Collection RemoveInvalidApiDescriptions(Collection apiDescriptions)
{
HashSet duplicateApiDescriptionIds = new HashSet(StringComparer.OrdinalIgnoreCase);
HashSet visitedApiDescriptionIds = new HashSet(StringComparer.OrdinalIgnoreCase);
foreach (ApiDescription description in apiDescriptions)
{
string apiDescriptionId = description.ID;
if (visitedApiDescriptionIds.Contains(apiDescriptionId))
{
duplicateApiDescriptionIds.Add(apiDescriptionId);
}
else
{
visitedApiDescriptionIds.Add(apiDescriptionId);
}
}
Collection filteredApiDescriptions = new Collection();
foreach (ApiDescription apiDescription in apiDescriptions)
{
string apiDescriptionId = apiDescription.ID;
if (!duplicateApiDescriptionIds.Contains(apiDescriptionId))
{
filteredApiDescriptions.Add(apiDescription);
}
}
return filteredApiDescriptions;
}
private static bool MatchRegexConstraint(IHttpRoute route, string parameterName, string parameterValue)
{
IDictionary constraints = route.Constraints;
if (constraints != null)
{
object constraint;
if (constraints.TryGetValue(parameterName, out constraint))
{
// treat the constraint as a string which represents a Regex.
// note that we don't support custom constraint (IHttpRouteConstraint) because it might rely on the request and some runtime states
string constraintsRule = constraint as string;
if (constraintsRule != null)
{
string constraintsRegEx = "^(" + constraintsRule + ")$";
return parameterValue != null && Regex.IsMatch(parameterValue, constraintsRegEx, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
}
}
}
return true;
}
private static HttpActionBinding GetActionBinding(HttpActionDescriptor actionDescriptor)
{
HttpControllerDescriptor controllerDescriptor = actionDescriptor.ControllerDescriptor;
if (controllerDescriptor == null)
{
return null;
}
ServicesContainer controllerServices = controllerDescriptor.Configuration.Services;
IActionValueBinder actionValueBinder = controllerServices.GetActionValueBinder();
HttpActionBinding actionBinding = actionValueBinder != null ? actionValueBinder.GetBinding(actionDescriptor) : null;
return actionBinding;
}
private sealed class ApiDescriptionComparer : IEqualityComparer
{
public bool Equals(ApiDescription x, ApiDescription y)
{
return String.Equals(x.ID, y.ID, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(ApiDescription obj)
{
return obj.ID.ToUpperInvariant().GetHashCode();
}
}
}
}