Streamlining action selection and route values

Removes a few concepts we don't need
Improves documentation
More things are IDictionary instead of other random types
This commit is contained in:
Ryan Nowak 2016-04-20 21:09:38 -07:00
parent 74a74fb3a8
commit af58c2e6b6
33 changed files with 420 additions and 679 deletions

View File

@ -3,15 +3,14 @@
using System; using System;
using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.Routing;
namespace ActionConstraintSample.Web namespace ActionConstraintSample.Web
{ {
public class CountrySpecificAttribute : RouteConstraintAttribute, IActionConstraint public class CountrySpecificAttribute : Attribute, IActionConstraint
{ {
private readonly string _countryCode; private readonly string _countryCode;
public CountrySpecificAttribute(string countryCode) public CountrySpecificAttribute(string countryCode)
: base("country", countryCode, blockNonAttributedActions: false)
{ {
_countryCode = countryCode; _countryCode = countryCode;
} }

View File

@ -5,10 +5,10 @@ using Microsoft.AspNetCore.Mvc.Routing;
namespace CustomRouteSample.Web namespace CustomRouteSample.Web
{ {
public class LocaleAttribute : RouteConstraintAttribute public class LocaleAttribute : RouteValueAttribute
{ {
public LocaleAttribute(string locale) public LocaleAttribute(string locale)
: base("locale", routeValue: locale, blockNonAttributedActions: true) : base("locale", routeValue: locale)
{ {
} }
} }

View File

@ -7,10 +7,10 @@ using Microsoft.AspNetCore.Mvc.Routing;
namespace MvcSubAreaSample.Web namespace MvcSubAreaSample.Web
{ {
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class SubAreaAttribute : RouteConstraintAttribute public class SubAreaAttribute : RouteValueAttribute
{ {
public SubAreaAttribute(string name) public SubAreaAttribute(string name)
: base("subarea", name, blockNonAttributedActions: true) : base("subarea", name)
{ {
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
{ {

View File

@ -29,12 +29,11 @@ namespace MvcSubAreaSample.Web
public void PopulateValues(ViewLocationExpanderContext context) public void PopulateValues(ViewLocationExpanderContext context)
{ {
var subArea = context.ActionContext.ActionDescriptor.RouteConstraints.FirstOrDefault( string subArea;
s => s.RouteKey == "subarea" && !string.IsNullOrEmpty(s.RouteValue)); if (context.ActionContext.ActionDescriptor.RouteValues.TryGetValue(_subAreaKey, out subArea) &&
!string.IsNullOrEmpty(subArea))
if (subArea != null)
{ {
context.Values[_subAreaKey] = subArea.RouteValue; context.Values[_subAreaKey] = subArea;
} }
} }
} }

View File

@ -13,9 +13,10 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions
{ {
public ActionDescriptor() public ActionDescriptor()
{ {
Properties = new Dictionary<object, object>();
RouteValueDefaults = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
Id = Guid.NewGuid().ToString(); Id = Guid.NewGuid().ToString();
Properties = new Dictionary<object, object>();
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
RouteValueDefaults = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
} }
/// <summary> /// <summary>
@ -25,7 +26,11 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions
public virtual string Name { get; set; } public virtual string Name { get; set; }
public IList<RouteDataActionConstraint> RouteConstraints { get; set; } /// <summary>
/// Gets or sets the collection of route values that must be provided by routing
/// for the action to be selected.
/// </summary>
public IDictionary<string, string> RouteValues { get; set; }
public AttributeRouteInfo AttributeRouteInfo { get; set; } public AttributeRouteInfo AttributeRouteInfo { get; set; }

View File

@ -1,63 +0,0 @@
// 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;
namespace Microsoft.AspNetCore.Mvc.Routing
{
/// <summary>
/// Constraints an action to a route key and value.
/// </summary>
public class RouteDataActionConstraint
{
private RouteDataActionConstraint(string routeKey)
{
if (routeKey == null)
{
throw new ArgumentNullException(nameof(routeKey));
}
RouteKey = routeKey;
}
/// <summary>
/// Initializes a <see cref="RouteDataActionConstraint"/> with a key and value, that are
/// required to make the action match.
/// </summary>
/// <param name="routeKey">The route key.</param>
/// <param name="routeValue">The route value.</param>
/// <remarks>
/// Passing a <see cref="string.Empty"/> or <see langword="null" /> to <paramref name="routeValue"/>
/// is a way to express that routing cannot produce a value for this key.
/// </remarks>
public RouteDataActionConstraint(string routeKey, string routeValue)
: this(routeKey)
{
RouteValue = routeValue ?? string.Empty;
if (string.IsNullOrEmpty(routeValue))
{
KeyHandling = RouteKeyHandling.DenyKey;
}
else
{
KeyHandling = RouteKeyHandling.RequireKey;
}
}
/// <summary>
/// The route key this constraint matches against.
/// </summary>
public string RouteKey { get; private set; }
/// <summary>
/// The route value this constraint matches against.
/// </summary>
public string RouteValue { get; private set; }
/// <summary>
/// The key handling definition for this constraint.
/// </summary>
public RouteKeyHandling KeyHandling { get; private set; }
}
}

View File

@ -1,18 +0,0 @@
// 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.
namespace Microsoft.AspNetCore.Mvc.Routing
{
public enum RouteKeyHandling
{
/// <summary>
/// Requires that the key will be in the route values, and that the content matches.
/// </summary>
RequireKey,
/// <summary>
/// Requires that the key will not be in the route values.
/// </summary>
DenyKey,
}
}

View File

@ -6,8 +6,9 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{ {
@ -34,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
Attributes = new List<object>(attributes); Attributes = new List<object>(attributes);
Filters = new List<IFilterMetadata>(); Filters = new List<IFilterMetadata>();
Parameters = new List<ParameterModel>(); Parameters = new List<ParameterModel>();
RouteConstraints = new List<IRouteConstraintProvider>(); RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
Properties = new Dictionary<object, object>(); Properties = new Dictionary<object, object>();
Selectors = new List<SelectorModel>(); Selectors = new List<SelectorModel>();
} }
@ -56,11 +57,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
Attributes = new List<object>(other.Attributes); Attributes = new List<object>(other.Attributes);
Filters = new List<IFilterMetadata>(other.Filters); Filters = new List<IFilterMetadata>(other.Filters);
Properties = new Dictionary<object, object>(other.Properties); Properties = new Dictionary<object, object>(other.Properties);
RouteValues = new Dictionary<string, string>(other.RouteValues, StringComparer.OrdinalIgnoreCase);
// Make a deep copy of other 'model' types. // Make a deep copy of other 'model' types.
ApiExplorer = new ApiExplorerModel(other.ApiExplorer); ApiExplorer = new ApiExplorerModel(other.ApiExplorer);
Parameters = new List<ParameterModel>(other.Parameters.Select(p => new ParameterModel(p))); Parameters = new List<ParameterModel>(other.Parameters.Select(p => new ParameterModel(p)));
RouteConstraints = new List<IRouteConstraintProvider>(other.RouteConstraints);
Selectors = new List<SelectorModel>(other.Selectors.Select(s => new SelectorModel(s))); Selectors = new List<SelectorModel>(other.Selectors.Select(s => new SelectorModel(s)));
} }
@ -88,7 +89,24 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public IList<ParameterModel> Parameters { get; } public IList<ParameterModel> Parameters { get; }
public IList<IRouteConstraintProvider> RouteConstraints { get; } /// <summary>
/// Gets a collection of route values that must be present in the
/// <see cref="RouteData.Values"/> for the corresponding action to be selected.
/// </summary>
/// <remarks>
/// <para>
/// The value of <see cref="ActionName"/> is considered an implicit route value corresponding
/// to the key <c>action</c> and the value of <see cref="ControllerModel.ControllerName"/> is
/// considered an implicit route value corresponding to the key <c>controller</c>. These entries
/// will be added to <see cref="ActionDescriptor.RouteValues"/>, but will not be visible in
/// <see cref="RouteValues"/>.
/// </para>
/// <para>
/// Entries in <see cref="RouteValues"/> can override entries in
/// <see cref="ControllerModel.RouteValues"/>.
/// </para>
/// </remarks>
public IDictionary<string, string> RouteValues { get; }
/// <summary> /// <summary>
/// Gets a set of properties associated with the action. /// Gets a set of properties associated with the action.

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{ {
@ -36,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
ControllerProperties = new List<PropertyModel>(); ControllerProperties = new List<PropertyModel>();
Filters = new List<IFilterMetadata>(); Filters = new List<IFilterMetadata>();
Properties = new Dictionary<object, object>(); Properties = new Dictionary<object, object>();
RouteConstraints = new List<IRouteConstraintProvider>(); RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
Selectors = new List<SelectorModel>(); Selectors = new List<SelectorModel>();
} }
@ -56,7 +57,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
// These are just metadata, safe to create new collections // These are just metadata, safe to create new collections
Attributes = new List<object>(other.Attributes); Attributes = new List<object>(other.Attributes);
Filters = new List<IFilterMetadata>(other.Filters); Filters = new List<IFilterMetadata>(other.Filters);
RouteConstraints = new List<IRouteConstraintProvider>(other.RouteConstraints); RouteValues = new Dictionary<string, string>(other.RouteValues, StringComparer.OrdinalIgnoreCase);
Properties = new Dictionary<object, object>(other.Properties); Properties = new Dictionary<object, object>(other.Properties);
// Make a deep copy of other 'model' types. // Make a deep copy of other 'model' types.
@ -97,7 +98,15 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public IList<IFilterMetadata> Filters { get; } public IList<IFilterMetadata> Filters { get; }
public IList<IRouteConstraintProvider> RouteConstraints { get; } /// <summary>
/// Gets a collection of route values that must be present in the
/// <see cref="RouteData.Values"/> for the corresponding action to be selected.
/// </summary>
/// <remarks>
/// Entries in <see cref="RouteValues"/> can be overridden by entries in
/// <see cref="ActionModel.RouteValues"/>.
/// </remarks>
public IDictionary<string, string> RouteValues { get; }
/// <summary> /// <summary>
/// Gets a set of properties associated with the controller. /// Gets a set of properties associated with the controller.

View File

@ -7,10 +7,10 @@ using Microsoft.AspNetCore.Mvc.Routing;
namespace Microsoft.AspNetCore.Mvc namespace Microsoft.AspNetCore.Mvc
{ {
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AreaAttribute : RouteConstraintAttribute public class AreaAttribute : RouteValueAttribute
{ {
public AreaAttribute(string areaName) public AreaAttribute(string areaName)
: base("area", areaName, blockNonAttributedActions: true) : base("area", areaName)
{ {
if (string.IsNullOrEmpty(areaName)) if (string.IsNullOrEmpty(areaName))
{ {

View File

@ -83,36 +83,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
var results = new Dictionary<string, DecisionCriterionValue>(StringComparer.OrdinalIgnoreCase); var results = new Dictionary<string, DecisionCriterionValue>(StringComparer.OrdinalIgnoreCase);
if (item.RouteConstraints != null) if (item.RouteValues != null)
{ {
foreach (var constraint in item.RouteConstraints) foreach (var kvp in item.RouteValues)
{ {
DecisionCriterionValue value; // null and string.Empty are equivalent for route values, so just treat nulls as
if (constraint.KeyHandling == RouteKeyHandling.DenyKey) // string.Empty.
{ results.Add(kvp.Key, new DecisionCriterionValue(kvp.Value ?? string.Empty));
// null and string.Empty are equivalent for route values, so just treat nulls as
// string.Empty.
value = new DecisionCriterionValue(value: string.Empty);
}
else if (constraint.KeyHandling == RouteKeyHandling.RequireKey)
{
value = new DecisionCriterionValue(value: constraint.RouteValue);
}
else
{
// We'd already have failed before getting here. The RouteDataActionConstraint constructor
// would throw.
#if NET451
throw new InvalidEnumArgumentException(
nameof(item),
(int)constraint.KeyHandling,
typeof(RouteKeyHandling));
#else
throw new ArgumentOutOfRangeException(nameof(item));
#endif
}
results.Add(constraint.RouteKey, value);
} }
} }

View File

@ -48,14 +48,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
var tree = _decisionTreeProvider.DecisionTree; var tree = _decisionTreeProvider.DecisionTree;
var matchingRouteConstraints = tree.Select(context.RouteData.Values); var matchingRouteValues = tree.Select(context.RouteData.Values);
var candidates = new List<ActionSelectorCandidate>(); var candidates = new List<ActionSelectorCandidate>();
// Perf: Avoid allocations // Perf: Avoid allocations
for (var i = 0; i < matchingRouteConstraints.Count; i++) for (var i = 0; i < matchingRouteValues.Count; i++)
{ {
var action = matchingRouteConstraints[i]; var action = matchingRouteValues[i];
var constraints = _actionConstraintCache.GetActionConstraints(context.HttpContext, action); var constraints = _actionConstraintCache.GetActionConstraints(context.HttpContext, action);
candidates.Add(new ActionSelectorCandidate(action, constraints)); candidates.Add(new ActionSelectorCandidate(action, constraints));
} }

View File

@ -179,11 +179,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Dictionary<string, RouteTemplate> templateCache, Dictionary<string, RouteTemplate> templateCache,
ActionDescriptor action) ActionDescriptor action)
{ {
var constraint = action.RouteConstraints string value;
.FirstOrDefault(c => c.RouteKey == TreeRouter.RouteGroupKey); action.RouteValues.TryGetValue(TreeRouter.RouteGroupKey, out value);
if (constraint == null ||
constraint.KeyHandling != RouteKeyHandling.RequireKey || if (string.IsNullOrEmpty(value))
constraint.RouteValue == null)
{ {
// This can happen if an ActionDescriptor has a route template, but doesn't have one of our // This can happen if an ActionDescriptor has a route template, but doesn't have one of our
// special route group constraints. This is a good indication that the user is using a 3rd party // special route group constraints. This is a good indication that the user is using a 3rd party
@ -196,7 +195,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var routeInfo = new RouteInfo() var routeInfo = new RouteInfo()
{ {
ActionDescriptor = action, ActionDescriptor = action,
RouteGroup = constraint.RouteValue, RouteGroup = value,
}; };
try try

View File

@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actions = new List<ControllerActionDescriptor>(); var actions = new List<ControllerActionDescriptor>();
var hasAttributeRoutes = false; var hasAttributeRoutes = false;
var removalConstraints = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var routeValueKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var methodInfoMap = new MethodToActionMap(); var methodInfoMap = new MethodToActionMap();
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
actionDescriptor.ControllerTypeInfo = controller.ControllerType; actionDescriptor.ControllerTypeInfo = controller.ControllerType;
AddApiExplorerInfo(actionDescriptor, application, controller, action); AddApiExplorerInfo(actionDescriptor, application, controller, action);
AddRouteConstraints(removalConstraints, actionDescriptor, controller, action); AddRouteValues(routeValueKeys, actionDescriptor, controller, action);
AddProperties(actionDescriptor, action, controller, application); AddProperties(actionDescriptor, action, controller, application);
actionDescriptor.BoundProperties = controllerPropertyDescriptors; actionDescriptor.BoundProperties = controllerPropertyDescriptors;
@ -77,15 +77,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// An attribute routed action will ignore conventional routed constraints. We still // An attribute routed action will ignore conventional routed constraints. We still
// want to provide these values as ambient values for link generation. // want to provide these values as ambient values for link generation.
AddConstraintsAsDefaultRouteValues(actionDescriptor); AddRouteValuesAsDefaultRouteValues(actionDescriptor);
// Replaces tokens like [controller]/[action] in the route template with the actual values // Replaces tokens like [controller]/[action] in the route template with the actual values
// for this action. // for this action.
ReplaceAttributeRouteTokens(actionDescriptor, routeTemplateErrors); ReplaceAttributeRouteTokens(actionDescriptor, routeTemplateErrors);
// Attribute routed actions will ignore conventional routed constraints. Instead they have // Attribute routed actions will ignore conventional routed values. Instead they have
// a single route constraint "RouteGroup" associated with it. // a single route value "RouteGroup" associated with it.
ReplaceRouteConstraints(actionDescriptor); ReplaceRouteValues(actionDescriptor);
} }
} }
@ -119,16 +119,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// selected when a route group returned by the route. // selected when a route group returned by the route.
if (hasAttributeRoutes) if (hasAttributeRoutes)
{ {
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( actionDescriptor.RouteValues.Add(TreeRouter.RouteGroupKey, string.Empty);
TreeRouter.RouteGroupKey,
string.Empty));
} }
// Add a route constraint with DenyKey for each constraint in the set to all the // Add a route value with 'null' for each user-defined route value in the set to all the
// actions that don't have that constraint. For example, if a controller defines // actions that don't have that value. For example, if a controller defines
// an area constraint, all actions that don't belong to an area must have a route // an area, all actions that don't belong to an area must have a route
// constraint that prevents them from matching an incoming request. // value that prevents them from matching an incoming request when area is specified.
AddRemovalConstraints(actionDescriptor, removalConstraints); AddGlobalRouteValues(actionDescriptor, routeValueKeys);
} }
else else
{ {
@ -145,7 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// //
// Consider an action like { area = "", controller = "Home", action = "Index" }. Even if // Consider an action like { area = "", controller = "Home", action = "Index" }. Even if
// it's attribute routed, it needs to know that area must be null to generate a link. // it's attribute routed, it needs to know that area must be null to generate a link.
foreach (var key in removalConstraints) foreach (var key in routeValueKeys)
{ {
if (!actionDescriptor.RouteValueDefaults.ContainsKey(key)) if (!actionDescriptor.RouteValueDefaults.ContainsKey(key))
{ {
@ -287,7 +285,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Name = action.ActionName, Name = action.ActionName,
MethodInfo = action.ActionMethod, MethodInfo = action.ActionMethod,
Parameters = parameterDescriptors, Parameters = parameterDescriptors,
RouteConstraints = new List<RouteDataActionConstraint>(),
AttributeRouteInfo = CreateAttributeRouteInfo(actionAttributeRoute, controllerAttributeRoute) AttributeRouteInfo = CreateAttributeRouteInfo(actionAttributeRoute, controllerAttributeRoute)
}; };
@ -448,8 +445,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
} }
public static void AddRouteConstraints( public static void AddRouteValues(
ISet<string> removalConstraints, ISet<string> keys,
ControllerActionDescriptor actionDescriptor, ControllerActionDescriptor actionDescriptor,
ControllerModel controller, ControllerModel controller,
ActionModel action) ActionModel action)
@ -459,72 +456,48 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// without the constraint to match. For example, actions without an [Area] attribute on their // without the constraint to match. For example, actions without an [Area] attribute on their
// controller should not match when a value has been given for area when matching a url or // controller should not match when a value has been given for area when matching a url or
// generating a link. // generating a link.
foreach (var constraintAttribute in action.RouteConstraints) foreach (var kvp in action.RouteValues)
{ {
if (constraintAttribute.BlockNonAttributedActions) keys.Add(kvp.Key);
{
removalConstraints.Add(constraintAttribute.RouteKey);
}
// Skip duplicates // Skip duplicates
if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey)) if (!actionDescriptor.RouteValues.ContainsKey(kvp.Key))
{ {
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value);
constraintAttribute.RouteKey,
constraintAttribute.RouteValue));
} }
} }
foreach (var constraintAttribute in controller.RouteConstraints) foreach (var kvp in controller.RouteValues)
{ {
if (constraintAttribute.BlockNonAttributedActions) keys.Add(kvp.Key);
{
removalConstraints.Add(constraintAttribute.RouteKey);
}
// Skip duplicates - this also means that a value on the action will take precedence // Skip duplicates - this also means that a value on the action will take precedence
if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey)) if (!actionDescriptor.RouteValues.ContainsKey(kvp.Key))
{ {
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value);
constraintAttribute.RouteKey,
constraintAttribute.RouteValue));
} }
} }
// Lastly add the 'default' values // Lastly add the 'default' values
if (!HasConstraint(actionDescriptor.RouteConstraints, "action")) if (!actionDescriptor.RouteValues.ContainsKey("action"))
{ {
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( actionDescriptor.RouteValues.Add("action", action.ActionName ?? string.Empty);
"action",
action.ActionName ?? string.Empty));
} }
if (!HasConstraint(actionDescriptor.RouteConstraints, "controller")) if (!actionDescriptor.RouteValues.ContainsKey("controller"))
{ {
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( actionDescriptor.RouteValues.Add("controller", controller.ControllerName);
"controller",
controller.ControllerName));
} }
} }
private static bool HasConstraint(IList<RouteDataActionConstraint> constraints, string routeKey) private static void ReplaceRouteValues(ControllerActionDescriptor actionDescriptor)
{
return constraints.Any(
rc => string.Equals(rc.RouteKey, routeKey, StringComparison.OrdinalIgnoreCase));
}
private static void ReplaceRouteConstraints(ControllerActionDescriptor actionDescriptor)
{ {
var routeGroupValue = GetRouteGroupValue( var routeGroupValue = GetRouteGroupValue(
actionDescriptor.AttributeRouteInfo.Order, actionDescriptor.AttributeRouteInfo.Order,
actionDescriptor.AttributeRouteInfo.Template); actionDescriptor.AttributeRouteInfo.Template);
var routeConstraints = new List<RouteDataActionConstraint>(); actionDescriptor.RouteValues.Clear();
routeConstraints.Add(new RouteDataActionConstraint( actionDescriptor.RouteValues.Add(TreeRouter.RouteGroupKey, routeGroupValue);
TreeRouter.RouteGroupKey,
routeGroupValue));
actionDescriptor.RouteConstraints = routeConstraints;
} }
private static void ReplaceAttributeRouteTokens( private static void ReplaceAttributeRouteTokens(
@ -557,31 +530,23 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
} }
private static void AddConstraintsAsDefaultRouteValues(ControllerActionDescriptor actionDescriptor) private static void AddRouteValuesAsDefaultRouteValues(ControllerActionDescriptor actionDescriptor)
{ {
foreach (var constraint in actionDescriptor.RouteConstraints) foreach (var kvp in actionDescriptor.RouteValues)
{ {
// We don't need to do anything with attribute routing for 'catch all' behavior. Order actionDescriptor.RouteValueDefaults.Add(kvp.Key, kvp.Value);
// and precedence of attribute routes allow this kind of behavior.
if (constraint.KeyHandling == RouteKeyHandling.RequireKey ||
constraint.KeyHandling == RouteKeyHandling.DenyKey)
{
actionDescriptor.RouteValueDefaults.Add(constraint.RouteKey, constraint.RouteValue);
}
} }
} }
private static void AddRemovalConstraints( private static void AddGlobalRouteValues(
ControllerActionDescriptor actionDescriptor, ControllerActionDescriptor actionDescriptor,
ISet<string> removalConstraints) ISet<string> removalConstraints)
{ {
foreach (var key in removalConstraints) foreach (var key in removalConstraints)
{ {
if (!HasConstraint(actionDescriptor.RouteConstraints, key)) if (!actionDescriptor.RouteValues.ContainsKey(key))
{ {
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( actionDescriptor.RouteValues.Add(key, string.Empty);
key,
string.Empty));
} }
} }
} }

View File

@ -170,7 +170,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
typeInfo.Name; typeInfo.Name;
AddRange(controllerModel.Filters, attributes.OfType<IFilterMetadata>()); AddRange(controllerModel.Filters, attributes.OfType<IFilterMetadata>());
AddRange(controllerModel.RouteConstraints, attributes.OfType<IRouteConstraintProvider>());
foreach (var routeValueProvider in attributes.OfType<IRouteValueProvider>())
{
controllerModel.RouteValues.Add(routeValueProvider.RouteKey, routeValueProvider.RouteValue);
}
var apiVisibility = attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault(); var apiVisibility = attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault();
if (apiVisibility != null) if (apiVisibility != null)
@ -285,8 +289,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
actionModel.ApiExplorer.GroupName = apiGroupName.GroupName; actionModel.ApiExplorer.GroupName = apiGroupName.GroupName;
} }
AddRange(actionModel.RouteConstraints, attributes.OfType<IRouteConstraintProvider>()); foreach (var routeValueProvider in attributes.OfType<IRouteValueProvider>())
{
actionModel.RouteValues.Add(routeValueProvider.RouteKey, routeValueProvider.RouteValue);
}
//TODO: modify comment //TODO: modify comment
// Now we need to determine the action selection info (cross-section of routes and constraints) // Now we need to determine the action selection info (cross-section of routes and constraints)
// //

View File

@ -1,78 +0,0 @@
// 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;
namespace Microsoft.AspNetCore.Mvc.Routing
{
/// <summary>
/// An attribute which specifies a required route value for an action or controller.
///
/// When placed on an action, the route data of a request must match the expectations of the route
/// constraint in order for the action to be selected. See <see cref="RouteKeyHandling"/> for
/// the expectations that must be satisfied by the route data.
///
/// When placed on a controller, unless overridden by the action, the constraint applies to all
/// actions defined by the controller.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class RouteConstraintAttribute : Attribute, IRouteConstraintProvider
{
/// <summary>
/// Creates a new <see cref="RouteConstraintAttribute"/> with <see cref="RouteKeyHandling"/> set as
/// <see cref="RouteKeyHandling.DenyKey"/>.
/// </summary>
/// <param name="routeKey">The route value key.</param>
protected RouteConstraintAttribute(string routeKey)
{
if (routeKey == null)
{
throw new ArgumentNullException(nameof(routeKey));
}
RouteKey = routeKey;
RouteKeyHandling = RouteKeyHandling.DenyKey;
}
/// <summary>
/// Creates a new <see cref="RouteConstraintAttribute"/> with
/// <see cref="RouteConstraintAttribute.RouteKeyHandling"/> set to <see cref="RouteKeyHandling.RequireKey"/>.
/// </summary>
/// <param name="routeKey">The route value key.</param>
/// <param name="routeValue">The expected route value.</param>
/// <param name="blockNonAttributedActions">
/// Set to true to negate this constraint on all actions that do not define a behavior for this route key.
/// </param>
protected RouteConstraintAttribute(
string routeKey,
string routeValue,
bool blockNonAttributedActions)
{
if (routeKey == null)
{
throw new ArgumentNullException(nameof(routeKey));
}
if (routeValue == null)
{
throw new ArgumentNullException(nameof(routeValue));
}
RouteKey = routeKey;
RouteValue = routeValue;
BlockNonAttributedActions = blockNonAttributedActions;
}
/// <inheritdoc />
public string RouteKey { get; private set; }
/// <inheritdoc />
public RouteKeyHandling RouteKeyHandling { get; private set; }
/// <inheritdoc />
public string RouteValue { get; private set; }
/// <inheritdoc />
public bool BlockNonAttributedActions { get; private set; }
}
}

View File

@ -1,33 +0,0 @@
// 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.
namespace Microsoft.AspNetCore.Mvc.Routing
{
/// <summary>
/// An interface for metadata which provides <see cref="RouteDataActionConstraint"/> values
/// for a controller or action.
/// </summary>
public interface IRouteConstraintProvider
{
/// <summary>
/// The route value key.
/// </summary>
string RouteKey { get; }
/// <summary>
/// The <see cref="RouteKeyHandling"/>.
/// </summary>
RouteKeyHandling RouteKeyHandling { get; }
/// <summary>
/// The expected route value. Will be null unless <see cref="RouteKeyHandling"/> is
/// set to <see cref="RouteKeyHandling.RequireKey"/>.
/// </summary>
string RouteValue { get; }
/// <summary>
/// Set to true to negate this constraint on all actions that do not define a behavior for this route key.
/// </summary>
bool BlockNonAttributedActions { get; }
}
}

View File

@ -0,0 +1,85 @@
// 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 Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
namespace Microsoft.AspNetCore.Mvc.Routing
{
/// <summary>
/// <para>
/// A metadata interface which specifies a route value which is required for the action selector to
/// choose an action. When applied to an action using attribute routing, the route value will be added
/// to the <see cref="RouteData.Values"/> when the action is selected.
/// </para>
/// <para>
/// When an <see cref="IRouteValueProvider"/> is used to provide a new route value to an action, all
/// actions in the application must also have a value associated with that key, or have an implicit value
/// of <c>null</c>. See remarks for more details.
/// </para>
/// </summary>
/// <remarks>
/// <para>
/// The typical scheme for action selection in an MVC application is that an action will require the
/// matching values for its <see cref="ControllerActionDescriptor.ControllerName"/> and
/// <see cref="ActionDescriptor.Name"/>
/// </para>
/// <example>
/// For an action like <code>MyApp.Controllers.HomeController.Index()</code>, in order to be selected, the
/// <see cref="RouteData.Values"/> must contain the values
/// {
/// "action": "Index",
/// "controller": "Home"
/// }
/// </example>
/// <para>
/// If areas are in use in the application (see <see cref="AreaAttribute"/> which implements
/// <see cref="IRouteValueProvider"/>) then all actions are consider either in an area by having a
/// non-<c>null</c> area value (specified by <see cref="AreaAttribute"/> or another
/// <see cref="IRouteValueProvider"/>) or are considered 'outside' of areas by having the value <c>null</c>.
/// </para>
/// <example>
/// Consider an application with two controllers, each with an <code>Index</code> action method:
/// - <code>MyApp.Controllers.HomeController.Index()</code>
/// - <code>MyApp.Areas.Blog.Controllers.HomeController.Index()</code>
/// where <code>MyApp.Areas.Blog.Controllers.HomeController</code> has an area attribute
/// <code>[Area("Blog")]</code>.
///
/// For <see cref="RouteData.Values"/> like:
/// {
/// "action": "Index",
/// "controller": "Home"
/// }
///
/// <code>MyApp.Controllers.HomeController.Index()</code> will be selected.
/// <code>MyApp.Area.Blog.Controllers.HomeController.Index()</code> is not considered eligible because the
/// <see cref="RouteData.Values"/> does not contain the value 'Blog' for 'area'.
///
/// For <see cref="RouteData.Values"/> like:
/// {
/// "area": "Blog",
/// "action": "Index",
/// "controller": "Home"
/// }
///
/// <code>MyApp.Area.Blog.Controllers.HomeController.Index()</code> will be selected.
/// <code>MyApp.Controllers.HomeController.Index()</code> is not considered eligible because the route values
/// contain a value for 'area'. <code>MyApp.Controllers.HomeController.Index()</code> cannot match any value
/// for 'area' other than <c>null</c>.
/// </example>
/// </remarks>
public interface IRouteValueProvider
{
/// <summary>
/// The route value key.
/// </summary>
string RouteKey { get; }
/// <summary>
/// The route value. If <c>null</c> or empty, requires the route value associated with <see cref="RouteKey"/>
/// to be missing or <c>null</c>.
/// </summary>
string RouteValue { get; }
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Core;
@ -71,18 +72,20 @@ namespace Microsoft.AspNetCore.Mvc.Routing
if (valuesCollection == null || if (valuesCollection == null ||
version != valuesCollection.Version) version != valuesCollection.Version)
{ {
var routeValueCollection = var values = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
actionDescriptors for (var i = 0; i < actionDescriptors.Items.Count; i++)
.Items {
.Select(ad => ad.RouteConstraints.FirstOrDefault( var action = actionDescriptors.Items[i];
c => c.RouteKey == routeKey &&
c.KeyHandling == RouteKeyHandling.RequireKey))
.Where(rc => rc != null)
.Select(rc => rc.RouteValue)
.Distinct()
.ToArray();
valuesCollection = new RouteValuesCollection(version, routeValueCollection); string value;
if (action.RouteValues.TryGetValue(routeKey, out value) &&
!string.IsNullOrEmpty(value))
{
values.Add(value);
}
}
valuesCollection = new RouteValuesCollection(version, values.ToArray());
_cachedValuesCollection = valuesCollection; _cachedValuesCollection = valuesCollection;
} }

View File

@ -0,0 +1,50 @@
// 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;
namespace Microsoft.AspNetCore.Mvc.Routing
{
/// <summary>
/// An attribute which specifies a required route value for an action or controller.
///
/// When placed on an action, the route data of a request must match the expectations of the route
/// constraint in order for the action to be selected. See <see cref="IRouteValueProvider"/> for
/// the expectations that must be satisfied by the route data.
///
/// When placed on a controller, unless overridden by the action, the constraint applies to all
/// actions defined by the controller.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class RouteValueAttribute : Attribute, IRouteValueProvider
{
/// <summary>
/// Creates a new <see cref="RouteValueAttribute"/>.
/// </summary>
/// <param name="routeKey">The route value key.</param>
/// <param name="routeValue">The expected route value.</param>
protected RouteValueAttribute(
string routeKey,
string routeValue)
{
if (routeKey == null)
{
throw new ArgumentNullException(nameof(routeKey));
}
if (routeValue == null)
{
throw new ArgumentNullException(nameof(routeValue));
}
RouteKey = routeKey;
RouteValue = routeValue;
}
/// <inheritdoc />
public string RouteKey { get; }
/// <inheritdoc />
public string RouteValue { get; }
}
}

View File

@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
/// The casing of a route value in <see cref="ActionContext.RouteData"/> is determined by the client. /// The casing of a route value in <see cref="ActionContext.RouteData"/> is determined by the client.
/// This making constructing paths for view locations in a case sensitive file system unreliable. Using the /// This making constructing paths for view locations in a case sensitive file system unreliable. Using the
/// <see cref="Abstractions.ActionDescriptor.RouteValueDefaults"/> for attribute routes and /// <see cref="Abstractions.ActionDescriptor.RouteValueDefaults"/> for attribute routes and
/// <see cref="Abstractions.ActionDescriptor.RouteConstraints"/> for traditional routes to get route values /// <see cref="Abstractions.ActionDescriptor.RouteValues"/> for traditional routes to get route values
/// produces consistently cased results. /// produces consistently cased results.
/// </remarks> /// </remarks>
public static string GetNormalizedRouteValue(ActionContext context, string key) public static string GetNormalizedRouteValue(ActionContext context, string key)
@ -149,24 +149,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor
} }
else else
{ {
// Perf: Avoid allocations string value;
for (var i = 0; i < actionDescriptor.RouteConstraints.Count; i++) if (actionDescriptor.RouteValues.TryGetValue(key, out value) &&
!string.IsNullOrEmpty(value))
{ {
var constraint = actionDescriptor.RouteConstraints[i]; normalizedValue = value;
if (string.Equals(constraint.RouteKey, key, StringComparison.Ordinal))
{
if (constraint.KeyHandling == RouteKeyHandling.DenyKey)
{
return null;
}
else
{
normalizedValue = constraint.RouteValue;
}
// Duplicate keys in RouteConstraints are not allowed.
break;
}
} }
} }

View File

@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
var namedAction = action; var namedAction = action;
var unnamedAction = new ActionModel(namedAction); var unnamedAction = new ActionModel(namedAction);
unnamedAction.RouteConstraints.Add(new UnnamedActionRouteConstraint()); unnamedAction.RouteValues.Add("action", null);
newActions.Add(unnamedAction); newActions.Add(unnamedAction);
} }
} }
@ -114,23 +114,5 @@ namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
actionSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { "POST" })); actionSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { "POST" }));
} }
} }
private class UnnamedActionRouteConstraint : IRouteConstraintProvider
{
public UnnamedActionRouteConstraint()
{
RouteKey = "action";
RouteKeyHandling = RouteKeyHandling.DenyKey;
RouteValue = null;
}
public string RouteKey { get; }
public RouteKeyHandling RouteKeyHandling { get; }
public string RouteValue { get; }
public bool BlockNonAttributedActions { get; }
}
} }
} }

View File

@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
if (IsConventionApplicable(controller)) if (IsConventionApplicable(controller))
{ {
controller.RouteConstraints.Add(new AreaAttribute(_area)); controller.RouteValues.Add("area", _area);
} }
} }

View File

@ -64,10 +64,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
action.Selectors.Add(selectorModel); action.Selectors.Add(selectorModel);
action.ActionName = "Edit"; action.ActionName = "Edit";
action.Controller = new ControllerModel(typeof(TestController).GetTypeInfo(), action.Controller = new ControllerModel
new List<object>()); (typeof(TestController).GetTypeInfo(),
new List<object>());
action.Filters.Add(new MyFilterAttribute()); action.Filters.Add(new MyFilterAttribute());
action.RouteConstraints.Add(new MyRouteConstraintAttribute()); action.RouteValues.Add("key", "value");
action.Properties.Add(new KeyValuePair<object, object>("test key", "test value")); action.Properties.Add(new KeyValuePair<object, object>("test key", "test value"));
// Act // Act
@ -95,6 +96,13 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
// Ensure non-default value // Ensure non-default value
Assert.NotEmpty((IEnumerable<object>)value1); Assert.NotEmpty((IEnumerable<object>)value1);
} }
else if (typeof(IDictionary<string, string>).IsAssignableFrom(property.PropertyType))
{
Assert.Equal(value1, value2);
// Ensure non-default value
Assert.NotEmpty((IDictionary<string, string>)value1);
}
else if (typeof(IDictionary<object, object>).IsAssignableFrom(property.PropertyType)) else if (typeof(IDictionary<object, object>).IsAssignableFrom(property.PropertyType))
{ {
Assert.Equal(value1, value2); Assert.Equal(value1, value2);
@ -131,14 +139,10 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{ {
} }
private class MyRouteConstraintAttribute : Attribute, IRouteConstraintProvider private class MyRouteValueAttribute : Attribute, IRouteValueProvider
{ {
public bool BlockNonAttributedActions { get { return true; } }
public string RouteKey { get; set; } public string RouteKey { get; set; }
public RouteKeyHandling RouteKeyHandling { get { return RouteKeyHandling.RequireKey; } }
public string RouteValue { get; set; } public string RouteValue { get; set; }
} }
} }

View File

@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
Assert.NotSame(controller.Actions, controller2.Actions); Assert.NotSame(controller.Actions, controller2.Actions);
Assert.NotSame(controller.Attributes, controller2.Attributes); Assert.NotSame(controller.Attributes, controller2.Attributes);
Assert.NotSame(controller.Filters, controller2.Filters); Assert.NotSame(controller.Filters, controller2.Filters);
Assert.NotSame(controller.RouteConstraints, controller2.RouteConstraints); Assert.NotSame(controller.RouteValues, controller2.RouteValues);
} }
[Fact] [Fact]
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
controller.Application = new ApplicationModel(); controller.Application = new ApplicationModel();
controller.ControllerName = "cool"; controller.ControllerName = "cool";
controller.Filters.Add(new MyFilterAttribute()); controller.Filters.Add(new MyFilterAttribute());
controller.RouteConstraints.Add(new MyRouteConstraintAttribute()); controller.RouteValues.Add("key", "value");
controller.Properties.Add(new KeyValuePair<object, object>("test key", "test value")); controller.Properties.Add(new KeyValuePair<object, object>("test key", "test value"));
controller.ControllerProperties.Add( controller.ControllerProperties.Add(
new PropertyModel(typeof(TestController).GetProperty("TestProperty"), new List<object>())); new PropertyModel(typeof(TestController).GetProperty("TestProperty"), new List<object>()));
@ -99,6 +99,13 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
// Ensure non-default value // Ensure non-default value
Assert.NotEmpty((IEnumerable<object>)value1); Assert.NotEmpty((IEnumerable<object>)value1);
} }
else if (typeof(IDictionary<string, string>).IsAssignableFrom(property.PropertyType))
{
Assert.Equal(value1, value2);
// Ensure non-default value
Assert.NotEmpty((IDictionary<string, string>)value1);
}
else if (typeof(IDictionary<object, object>).IsAssignableFrom(property.PropertyType)) else if (typeof(IDictionary<object, object>).IsAssignableFrom(property.PropertyType))
{ {
Assert.Equal(value1, value2); Assert.Equal(value1, value2);
@ -137,14 +144,10 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{ {
} }
private class MyRouteConstraintAttribute : Attribute, IRouteConstraintProvider private class MyRouteValueAttribute : Attribute, IRouteValueProvider
{ {
public bool BlockNonAttributedActions { get { return true; } }
public string RouteKey { get; set; } public string RouteKey { get; set; }
public RouteKeyHandling RouteKeyHandling { get { return RouteKeyHandling.RequireKey; } }
public string RouteValue { get; set; } public string RouteValue { get; set; }
} }
} }

View File

@ -600,9 +600,9 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
return return
actions actions
.Where(a => a.RouteConstraints.Any(c => c.RouteKey == "area" && comparer.Equals(c.RouteValue, area))) .Where(a => a.RouteValues.Any(kvp => kvp.Key == "area" && comparer.Equals(kvp.Value, area)))
.Where(a => a.RouteConstraints.Any(c => c.RouteKey == "controller" && comparer.Equals(c.RouteValue, controller))) .Where(a => a.RouteValues.Any(kvp => kvp.Key == "controller" && comparer.Equals(kvp.Value, controller)))
.Where(a => a.RouteConstraints.Any(c => c.RouteKey == "action" && comparer.Equals(c.RouteValue, action))); .Where(a => a.RouteValues.Any(kvp => kvp.Key == "action" && comparer.Equals(kvp.Value, action)));
} }
private static ActionSelector CreateSelector(IReadOnlyList<ActionDescriptor> actions, ILoggerFactory loggerFactory = null) private static ActionSelector CreateSelector(IReadOnlyList<ActionDescriptor> actions, ILoggerFactory loggerFactory = null)
@ -667,24 +667,12 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
var actionDescriptor = new ActionDescriptor() var actionDescriptor = new ActionDescriptor()
{ {
Name = string.Format("Area: {0}, Controller: {1}, Action: {2}", area, controller, action), Name = string.Format("Area: {0}, Controller: {1}, Action: {2}", area, controller, action),
RouteConstraints = new List<RouteDataActionConstraint>(),
Parameters = new List<ParameterDescriptor>(), Parameters = new List<ParameterDescriptor>(),
}; };
actionDescriptor.RouteConstraints.Add( actionDescriptor.RouteValues.Add("area", area);
area == null ? actionDescriptor.RouteValues.Add("controller", controller);
new RouteDataActionConstraint("area", null) : actionDescriptor.RouteValues.Add("action", action);
new RouteDataActionConstraint("area", area));
actionDescriptor.RouteConstraints.Add(
controller == null ?
new RouteDataActionConstraint("controller", null) :
new RouteDataActionConstraint("controller", controller));
actionDescriptor.RouteConstraints.Add(
action == null ?
new RouteDataActionConstraint("action", null) :
new RouteDataActionConstraint("action", action));
return actionDescriptor; return actionDescriptor;
} }

View File

@ -1,41 +0,0 @@
// 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 Microsoft.AspNetCore.Mvc.Routing;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
public class RouteDataActionConstraintTest
{
[Fact]
public void RouteDataActionConstraint_DenyKeyByPassingEmptyString()
{
var routeDataConstraint = new RouteDataActionConstraint("key", string.Empty);
Assert.Equal(routeDataConstraint.RouteKey, "key");
Assert.Equal(routeDataConstraint.KeyHandling, RouteKeyHandling.DenyKey);
Assert.Equal(routeDataConstraint.RouteValue, string.Empty);
}
[Fact]
public void RouteDataActionConstraint_DenyKeyByPassingNull()
{
var routeDataConstraint = new RouteDataActionConstraint("key", null);
Assert.Equal(routeDataConstraint.RouteKey, "key");
Assert.Equal(routeDataConstraint.KeyHandling, RouteKeyHandling.DenyKey);
Assert.Equal(routeDataConstraint.RouteValue, string.Empty);
}
[Fact]
public void RouteDataActionConstraint_RequireKeyByPassingNonEmpty()
{
var routeDataConstraint = new RouteDataActionConstraint("key", "value");
Assert.Equal(routeDataConstraint.RouteKey, "key");
Assert.Equal(routeDataConstraint.KeyHandling, RouteKeyHandling.RequireKey);
Assert.Equal(routeDataConstraint.RouteValue, "value");
}
}
}

View File

@ -42,9 +42,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
Template = "api/Blog/{id}" Template = "api/Blog/{id}"
}, },
RouteConstraints = new List<RouteDataActionConstraint>() RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "1"), { TreeRouter.RouteGroupKey, "1" }
}, },
}, },
new ActionDescriptor() new ActionDescriptor()
@ -53,9 +53,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
Template = "api/Store/Buy/{id}" Template = "api/Store/Buy/{id}"
}, },
RouteConstraints = new List<RouteDataActionConstraint>() RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "2"), { TreeRouter.RouteGroupKey, "2" }
}, },
}, },
}; };
@ -116,9 +116,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Name = "BLOG_INDEX", Name = "BLOG_INDEX",
Order = 17, Order = 17,
}, },
RouteConstraints = new List<RouteDataActionConstraint>() RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "1"), { TreeRouter.RouteGroupKey, "1" }
}, },
RouteValueDefaults = new Dictionary<string, object>() RouteValueDefaults = new Dictionary<string, object>()
{ {
@ -164,9 +164,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Name = "BLOG_INDEX", Name = "BLOG_INDEX",
Order = 17, Order = 17,
}, },
RouteConstraints = new List<RouteDataActionConstraint>() RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "1"), { TreeRouter.RouteGroupKey, "1" }
}, },
RouteValueDefaults = new Dictionary<string, object>() RouteValueDefaults = new Dictionary<string, object>()
{ {
@ -212,9 +212,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Name = "BLOG_INDEX", Name = "BLOG_INDEX",
Order = 17, Order = 17,
}, },
RouteConstraints = new List<RouteDataActionConstraint>() RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "1"), { TreeRouter.RouteGroupKey, "1" }
}, },
RouteValueDefaults = new Dictionary<string, object>() RouteValueDefaults = new Dictionary<string, object>()
{ {
@ -263,9 +263,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Name = "BLOG_INDEX", Name = "BLOG_INDEX",
Order = 17, Order = 17,
}, },
RouteConstraints = new List<RouteDataActionConstraint>() RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "1"), { TreeRouter.RouteGroupKey, "1" }
}, },
RouteValueDefaults = new Dictionary<string, object>() RouteValueDefaults = new Dictionary<string, object>()
{ {
@ -281,9 +281,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Name = "BLOG_INDEX", Name = "BLOG_INDEX",
Order = 17, Order = 17,
}, },
RouteConstraints = new List<RouteDataActionConstraint>() RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "1"), { TreeRouter.RouteGroupKey, "1" }
}, },
RouteValueDefaults = new Dictionary<string, object>() RouteValueDefaults = new Dictionary<string, object>()
{ {
@ -339,9 +339,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Name = "BLOG_INDEX", Name = "BLOG_INDEX",
Order = 17, Order = 17,
}, },
RouteConstraints = new List<RouteDataActionConstraint>() RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "1"), { TreeRouter.RouteGroupKey, "1" }
}, },
RouteValueDefaults = new Dictionary<string, object>() RouteValueDefaults = new Dictionary<string, object>()
{ {
@ -388,9 +388,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Name = "BLOG_INDEX", Name = "BLOG_INDEX",
Order = 17, Order = 17,
}, },
RouteConstraints = new List<RouteDataActionConstraint>() RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "1"), { TreeRouter.RouteGroupKey, "1" }
}, },
RouteValueDefaults = new Dictionary<string, object>() RouteValueDefaults = new Dictionary<string, object>()
{ {
@ -437,9 +437,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Name = "BLOG_INDEX", Name = "BLOG_INDEX",
Order = 17, Order = 17,
}, },
RouteConstraints = new List<RouteDataActionConstraint>() RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "1"), { TreeRouter.RouteGroupKey, "1" }
}, },
RouteValueDefaults = new Dictionary<string, object>() RouteValueDefaults = new Dictionary<string, object>()
{ {
@ -490,9 +490,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Name = "BLOG_INDEX", Name = "BLOG_INDEX",
Order = 17, Order = 17,
}, },
RouteConstraints = new List<RouteDataActionConstraint>() RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "1"), { TreeRouter.RouteGroupKey, "1" }
}, },
RouteValueDefaults = new Dictionary<string, object>() RouteValueDefaults = new Dictionary<string, object>()
{ {
@ -508,9 +508,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Name = "BLOG_INDEX", Name = "BLOG_INDEX",
Order = 17, Order = 17,
}, },
RouteConstraints = new List<RouteDataActionConstraint>() RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "1"), { TreeRouter.RouteGroupKey, "1" }
}, },
RouteValueDefaults = new Dictionary<string, object>() RouteValueDefaults = new Dictionary<string, object>()
{ {

View File

@ -131,9 +131,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var action = new ControllerActionDescriptor(); var action = new ControllerActionDescriptor();
action.DisplayName = "Microsoft.AspNetCore.Mvc.Routing.AttributeRoutingTest+HomeController.Index"; action.DisplayName = "Microsoft.AspNetCore.Mvc.Routing.AttributeRoutingTest+HomeController.Index";
action.MethodInfo = actionMethod; action.MethodInfo = actionMethod;
action.RouteConstraints = new List<RouteDataActionConstraint>() action.RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "group"), { TreeRouter.RouteGroupKey, "group" }
}; };
action.AttributeRouteInfo = new AttributeRouteInfo(); action.AttributeRouteInfo = new AttributeRouteInfo();
action.AttributeRouteInfo.Template = "{controller}/{action}"; action.AttributeRouteInfo.Template = "{controller}/{action}";
@ -167,9 +167,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return new DisplayNameActionDescriptor() return new DisplayNameActionDescriptor()
{ {
DisplayName = displayName, DisplayName = displayName,
RouteConstraints = new List<RouteDataActionConstraint>() RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
new RouteDataActionConstraint(TreeRouter.RouteGroupKey, "whatever"), { TreeRouter.RouteGroupKey, "whatever" }
}, },
AttributeRouteInfo = new AttributeRouteInfo { Template = template }, AttributeRouteInfo = new AttributeRouteInfo { Template = template },
}; };

View File

@ -242,17 +242,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Assert // Assert
var action = Assert.Single(descriptors); var action = Assert.Single(descriptors);
Assert.NotNull(action.RouteConstraints); Assert.NotNull(action.RouteValues);
var controller = Assert.Single(action.RouteConstraints, var controller = Assert.Single(action.RouteValues, kvp => kvp.Key.Equals("controller"));
rc => rc.RouteKey.Equals("controller")); Assert.Equal("ConventionallyRouted", controller.Value);
Assert.Equal(RouteKeyHandling.RequireKey, controller.KeyHandling);
Assert.Equal("ConventionallyRouted", controller.RouteValue);
var actionConstraint = Assert.Single(action.RouteConstraints, var actionConstraint = Assert.Single(action.RouteValues, kvp => kvp.Key.Equals("action"));
rc => rc.RouteKey.Equals("action")); Assert.Equal(nameof(ConventionallyRoutedController.ConventionalAction), actionConstraint.Value);
Assert.Equal(RouteKeyHandling.RequireKey, actionConstraint.KeyHandling);
Assert.Equal(nameof(ConventionallyRoutedController.ConventionalAction), actionConstraint.RouteValue);
} }
[Fact] [Fact]
@ -265,132 +261,67 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Assert // Assert
var action = Assert.Single(descriptors); var action = Assert.Single(descriptors);
var routeconstraint = Assert.Single(action.RouteConstraints); Assert.Equal(TreeRouter.RouteGroupKey, Assert.Single(action.RouteValues).Key);
Assert.Equal(RouteKeyHandling.RequireKey, routeconstraint.KeyHandling);
Assert.Equal(TreeRouter.RouteGroupKey, routeconstraint.RouteKey);
var controller = Assert.Single(action.RouteValueDefaults, var controller = Assert.Single(action.RouteValueDefaults, kvp => kvp.Key.Equals("controller"));
rc => rc.Key.Equals("controller"));
Assert.Equal("AttributeRouted", controller.Value); Assert.Equal("AttributeRouted", controller.Value);
var actionConstraint = Assert.Single(action.RouteValueDefaults, var actionConstraint = Assert.Single(action.RouteValueDefaults, kvp => kvp.Key.Equals("action"));
rc => rc.Key.Equals("action"));
Assert.Equal(nameof(AttributeRoutedController.AttributeRoutedAction), actionConstraint.Value); Assert.Equal(nameof(AttributeRoutedController.AttributeRoutedAction), actionConstraint.Value);
} }
[Fact] [Fact]
public void GetDescriptors_WithRouteDataConstraint_WithBlockNonAttributedActions() public void GetDescriptors_WithRouteValueAttribute()
{ {
// Arrange & Act // Arrange & Act
var descriptors = GetDescriptors( var descriptors = GetDescriptors(
typeof(HttpMethodController).GetTypeInfo(), typeof(HttpMethodController).GetTypeInfo(),
typeof(BlockNonAttributedActionsController).GetTypeInfo()).ToArray(); typeof(RouteValueController).GetTypeInfo()).ToArray();
var descriptorWithoutConstraint = Assert.Single( var descriptorWithoutValue = Assert.Single(
descriptors, descriptors,
ad => ad.RouteConstraints.Any( ad => ad.RouteValues.Any(kvp => kvp.Key == "key" && string.IsNullOrEmpty(kvp.Value)));
c => c.RouteKey == "key" && c.KeyHandling == RouteKeyHandling.DenyKey));
var descriptorWithConstraint = Assert.Single( var descriptorWithValue = Assert.Single(
descriptors, descriptors,
ad => ad.RouteConstraints.Any( ad => ad.RouteValues.Any(kvp => kvp.Key == "key" && kvp.Value == "value"));
c =>
c.KeyHandling == RouteKeyHandling.RequireKey &&
c.RouteKey == "key" &&
c.RouteValue == "value"));
// Assert // Assert
Assert.Equal(2, descriptors.Length); Assert.Equal(2, descriptors.Length);
Assert.Equal(3, descriptorWithConstraint.RouteConstraints.Count); Assert.Equal(3, descriptorWithValue.RouteValues.Count);
Assert.Single( Assert.Single(
descriptorWithConstraint.RouteConstraints, descriptorWithValue.RouteValues,
c => c =>
c.RouteKey == "controller" && c.Key == "controller" &&
c.RouteValue == "BlockNonAttributedActions"); c.Value == "RouteValue");
Assert.Single( Assert.Single(
descriptorWithConstraint.RouteConstraints, descriptorWithValue.RouteValues,
c => c =>
c.RouteKey == "action" && c.Key == "action" &&
c.RouteValue == "Edit"); c.Value == "Edit");
Assert.Single( Assert.Single(
descriptorWithConstraint.RouteConstraints, descriptorWithValue.RouteValues,
c => c =>
c.RouteKey == "key" && c.Key == "key" &&
c.RouteValue == "value" && c.Value == "value");
c.KeyHandling == RouteKeyHandling.RequireKey);
Assert.Equal(3, descriptorWithoutConstraint.RouteConstraints.Count); Assert.Equal(3, descriptorWithoutValue.RouteValues.Count);
Assert.Single( Assert.Single(
descriptorWithoutConstraint.RouteConstraints, descriptorWithoutValue.RouteValues,
c => c =>
c.RouteKey == "controller" && c.Key == "controller" &&
c.RouteValue == "HttpMethod"); c.Value == "HttpMethod");
Assert.Single( Assert.Single(
descriptorWithoutConstraint.RouteConstraints, descriptorWithoutValue.RouteValues,
c => c =>
c.RouteKey == "action" && c.Key == "action" &&
c.RouteValue == "OnlyPost"); c.Value == "OnlyPost");
Assert.Single( Assert.Single(
descriptorWithoutConstraint.RouteConstraints, descriptorWithoutValue.RouteValues,
c => c =>
c.RouteKey == "key" && c.Key == "key" &&
c.RouteValue == string.Empty && c.Value == string.Empty);
c.KeyHandling == RouteKeyHandling.DenyKey);
}
[Fact]
public void GetDescriptors_WithRouteDataConstraint_WithoutBlockNonAttributedActions()
{
// Arrange & Act
var descriptors = GetDescriptors(
typeof(HttpMethodController).GetTypeInfo(),
typeof(DontBlockNonAttributedActionsController).GetTypeInfo()).ToArray();
var descriptorWithConstraint = Assert.Single(
descriptors,
ad => ad.RouteConstraints.Any(
c =>
c.KeyHandling == RouteKeyHandling.RequireKey &&
c.RouteKey == "key" &&
c.RouteValue == "value"));
var descriptorWithoutConstraint = Assert.Single(
descriptors,
ad => !ad.RouteConstraints.Any(c => c.RouteKey == "key"));
// Assert
Assert.Equal(2, descriptors.Length);
Assert.Equal(3, descriptorWithConstraint.RouteConstraints.Count);
Assert.Single(
descriptorWithConstraint.RouteConstraints,
c =>
c.RouteKey == "controller" &&
c.RouteValue == "DontBlockNonAttributedActions");
Assert.Single(
descriptorWithConstraint.RouteConstraints,
c =>
c.RouteKey == "action" &&
c.RouteValue == "Create");
Assert.Single(
descriptorWithConstraint.RouteConstraints,
c =>
c.RouteKey == "key" &&
c.RouteValue == "value" &&
c.KeyHandling == RouteKeyHandling.RequireKey);
Assert.Equal(2, descriptorWithoutConstraint.RouteConstraints.Count);
Assert.Single(
descriptorWithoutConstraint.RouteConstraints,
c =>
c.RouteKey == "controller" &&
c.RouteValue == "HttpMethod");
Assert.Single(
descriptorWithoutConstraint.RouteConstraints,
c =>
c.RouteKey == "action" &&
c.RouteValue == "OnlyPost");
} }
[Fact] [Fact]
@ -1002,10 +933,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
foreach (var actionDescriptor in actionDescriptors.Where(ad => ad.AttributeRouteInfo == null)) foreach (var actionDescriptor in actionDescriptors.Where(ad => ad.AttributeRouteInfo == null))
{ {
Assert.Equal(6, actionDescriptor.RouteConstraints.Count); Assert.Equal(6, actionDescriptor.RouteValues.Count);
var routeGroupConstraint = Assert.Single(actionDescriptor.RouteConstraints, Assert.Single(
rc => rc.RouteKey.Equals(TreeRouter.RouteGroupKey)); actionDescriptor.RouteValues,
Assert.Equal(RouteKeyHandling.DenyKey, routeGroupConstraint.KeyHandling); kvp => kvp.Key.Equals(TreeRouter.RouteGroupKey) && string.IsNullOrEmpty(kvp.Value));
} }
} }
@ -1026,11 +957,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var indexAction = Assert.Single(actionDescriptors, ad => ad.Name.Equals("Index")); var indexAction = Assert.Single(actionDescriptors, ad => ad.Name.Equals("Index"));
Assert.Equal(1, indexAction.RouteConstraints.Count); Assert.Equal(1, indexAction.RouteValues.Count);
var routeGroupConstraint = Assert.Single(indexAction.RouteConstraints, rc => rc.RouteKey.Equals(TreeRouter.RouteGroupKey)); var routeGroup = Assert.Single(indexAction.RouteValues, kvp => kvp.Key.Equals(TreeRouter.RouteGroupKey));
Assert.Equal(RouteKeyHandling.RequireKey, routeGroupConstraint.KeyHandling); Assert.NotNull(routeGroup.Value);
Assert.NotNull(routeGroupConstraint.RouteValue);
Assert.Equal(5, indexAction.RouteValueDefaults.Count); Assert.Equal(5, indexAction.RouteValueDefaults.Count);
@ -1043,11 +973,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var areaDefault = Assert.Single(indexAction.RouteValueDefaults, rd => rd.Key.Equals("area", StringComparison.OrdinalIgnoreCase)); var areaDefault = Assert.Single(indexAction.RouteValueDefaults, rd => rd.Key.Equals("area", StringComparison.OrdinalIgnoreCase));
Assert.Equal("Home", areaDefault.Value); Assert.Equal("Home", areaDefault.Value);
var myRouteConstraintDefault = Assert.Single(indexAction.RouteValueDefaults, rd => rd.Key.Equals("key", StringComparison.OrdinalIgnoreCase)); var mvRouteValueDefault = Assert.Single(indexAction.RouteValueDefaults, rd => rd.Key.Equals("key", StringComparison.OrdinalIgnoreCase));
Assert.Null(myRouteConstraintDefault.Value); Assert.Null(mvRouteValueDefault.Value);
var anotherRouteConstraint = Assert.Single(indexAction.RouteValueDefaults, rd => rd.Key.Equals("second", StringComparison.OrdinalIgnoreCase)); var anotherRouteValue = Assert.Single(indexAction.RouteValueDefaults, rd => rd.Key.Equals("second", StringComparison.OrdinalIgnoreCase));
Assert.Null(anotherRouteConstraint.Value); Assert.Null(anotherRouteValue.Value);
} }
[Fact] [Fact]
@ -1076,9 +1006,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actions = provider.GetDescriptors().ToArray(); var actions = provider.GetDescriptors().ToArray();
var groupIds = actions.Select( var groupIds = actions.Select(
a => a.RouteConstraints a => a.RouteValues
.Where(rc => rc.RouteKey == TreeRouter.RouteGroupKey) .Where(kvp => kvp.Key == TreeRouter.RouteGroupKey)
.Select(rc => rc.RouteValue) .Select(kvp => kvp.Value)
.Single()) .Single())
.ToArray(); .ToArray();
@ -1625,38 +1555,30 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ } { }
} }
private class MyRouteConstraintAttribute : RouteConstraintAttribute private class MyRouteValueAttribute : RouteValueAttribute
{ {
public MyRouteConstraintAttribute(bool blockNonAttributedActions) public MyRouteValueAttribute()
: base("key", "value", blockNonAttributedActions) : base("key", "value")
{ {
} }
} }
private class MySecondRouteConstraintAttribute : RouteConstraintAttribute private class MySecondRouteValueAttribute : RouteValueAttribute
{ {
public MySecondRouteConstraintAttribute(bool blockNonAttributedActions) public MySecondRouteValueAttribute()
: base("second", "value", blockNonAttributedActions) : base("second", "value")
{ {
} }
} }
[MyRouteConstraint(blockNonAttributedActions: true)] [MyRouteValue]
private class BlockNonAttributedActionsController private class RouteValueController
{ {
public void Edit() public void Edit()
{ {
} }
} }
[MyRouteConstraint(blockNonAttributedActions: false)]
private class DontBlockNonAttributedActionsController
{
public void Create()
{
}
}
private class MyFilterAttribute : Attribute, IFilterMetadata private class MyFilterAttribute : Attribute, IFilterMetadata
{ {
public MyFilterAttribute(int value) public MyFilterAttribute(int value)
@ -1677,7 +1599,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
[Route("api/Token/[key]/[controller]")] [Route("api/Token/[key]/[controller]")]
[MyRouteConstraint(false)] [MyRouteValue]
private class TokenReplacementController private class TokenReplacementController
{ {
[HttpGet("stub/[action]")] [HttpGet("stub/[action]")]
@ -1880,8 +1802,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public void DifferentHttpMethods() { } public void DifferentHttpMethods() { }
} }
[MyRouteConstraint(blockNonAttributedActions: true)] [MyRouteValue]
[MySecondRouteConstraint(blockNonAttributedActions: true)] [MySecondRouteValue]
private class ConstrainedController private class ConstrainedController
{ {
public void ConstrainedNonAttributedAction() { } public void ConstrainedNonAttributedAction() { }

View File

@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
var actionDescriptor = CreateActionDescriptor("testArea", var actionDescriptor = CreateActionDescriptor("testArea",
"testController", "testController",
"testAction"); "testAction");
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint("randomKey", "testRandom")); actionDescriptor.RouteValues.Add("randomKey", "testRandom");
var httpContext = GetHttpContext(actionDescriptor); var httpContext = GetHttpContext(actionDescriptor);
var route = Mock.Of<IRouter>(); var route = Mock.Of<IRouter>();
var values = new RouteValueDictionary() var values = new RouteValueDictionary()
@ -85,10 +85,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
public void RouteValue_DoesNotExists_MatchFails(string keyName, RouteDirection direction) public void RouteValue_DoesNotExists_MatchFails(string keyName, RouteDirection direction)
{ {
// Arrange // Arrange
var actionDescriptor = CreateActionDescriptor("testArea", var actionDescriptor = CreateActionDescriptor(
"testController", "testArea",
"testAction"); "testController",
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint("randomKey", "testRandom")); "testAction");
actionDescriptor.RouteValues.Add("randomKey", "testRandom");
var httpContext = GetHttpContext(actionDescriptor); var httpContext = GetHttpContext(actionDescriptor);
var route = Mock.Of<IRouter>(); var route = Mock.Of<IRouter>();
var values = new RouteValueDictionary() var values = new RouteValueDictionary()
@ -186,23 +187,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
var actionDescriptor = new ActionDescriptor() var actionDescriptor = new ActionDescriptor()
{ {
Name = string.Format("Area: {0}, Controller: {1}, Action: {2}", area, controller, action), Name = string.Format("Area: {0}, Controller: {1}, Action: {2}", area, controller, action),
RouteConstraints = new List<RouteDataActionConstraint>(),
}; };
actionDescriptor.RouteConstraints.Add( actionDescriptor.RouteValues.Add("area", area);
area == null ? actionDescriptor.RouteValues.Add("controller", controller);
new RouteDataActionConstraint("area", null) : actionDescriptor.RouteValues.Add("action", action);
new RouteDataActionConstraint("area", area));
actionDescriptor.RouteConstraints.Add(
controller == null ?
new RouteDataActionConstraint("controller", null) :
new RouteDataActionConstraint("controller", controller));
actionDescriptor.RouteConstraints.Add(
action == null ?
new RouteDataActionConstraint("action", null) :
new RouteDataActionConstraint("action", action));
return actionDescriptor; return actionDescriptor;
} }

View File

@ -1152,7 +1152,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
[Theory] [Theory]
// Looks in RouteValueDefaults // Looks in RouteValueDefaults
[InlineData(true)] [InlineData(true)]
// Looks in RouteConstraints // Looks in RouteValues
[InlineData(false)] [InlineData(false)]
public void FindPage_SelectsActionCaseInsensitively(bool isAttributeRouted) public void FindPage_SelectsActionCaseInsensitively(bool isAttributeRouted)
{ {
@ -1194,7 +1194,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
[Theory] [Theory]
// Looks in RouteValueDefaults // Looks in RouteValueDefaults
[InlineData(true)] [InlineData(true)]
// Looks in RouteConstraints // Looks in RouteValues
[InlineData(false)] [InlineData(false)]
public void FindPage_LooksForPages_UsingActionDescriptor_Controller(bool isAttributeRouted) public void FindPage_LooksForPages_UsingActionDescriptor_Controller(bool isAttributeRouted)
{ {
@ -1232,7 +1232,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
[Theory] [Theory]
// Looks in RouteValueDefaults // Looks in RouteValueDefaults
[InlineData(true)] [InlineData(true)]
// Looks in RouteConstraints // Looks in RouteValues
[InlineData(false)] [InlineData(false)]
public void FindPage_LooksForPages_UsingActionDescriptor_Areas(bool isAttributeRouted) public void FindPage_LooksForPages_UsingActionDescriptor_Areas(bool isAttributeRouted)
{ {
@ -1495,17 +1495,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
} }
[Fact] [Fact]
public void GetNormalizedRouteValue_ReturnsValueFromRouteConstraints_IfKeyHandlingIsRequired() public void GetNormalizedRouteValue_ReturnsValueFromRouteValues_IfKeyHandlingIsRequired()
{ {
// Arrange // Arrange
var key = "some-key"; var key = "some-key";
var actionDescriptor = new ActionDescriptor var actionDescriptor = new ActionDescriptor();
{ actionDescriptor.RouteValues.Add(key, "Route-Value");
RouteConstraints = new[]
{
new RouteDataActionConstraint(key, "Route-Value")
}
};
var actionContext = new ActionContext var actionContext = new ActionContext
{ {
@ -1523,17 +1518,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
} }
[Fact] [Fact]
public void GetNormalizedRouteValue_ReturnsRouteValue_IfValueDoesNotMatchRouteConstraint() public void GetNormalizedRouteValue_ReturnsRouteValue_IfValueDoesNotMatch()
{ {
// Arrange // Arrange
var key = "some-key"; var key = "some-key";
var actionDescriptor = new ActionDescriptor var actionDescriptor = new ActionDescriptor();
{ actionDescriptor.RouteValues.Add(key, "different-value");
RouteConstraints = new[]
{
new RouteDataActionConstraint(key, "different-value")
}
};
var actionContext = new ActionContext var actionContext = new ActionContext
{ {
@ -1551,17 +1541,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
} }
[Fact] [Fact]
public void GetNormalizedRouteValue_ReturnsNull_IfRouteConstraintKeyHandlingIsDeny() public void GetNormalizedRouteValue_ReturnsNonNormalizedValue_IfActionRouteValueIsNull()
{ {
// Arrange // Arrange
var key = "some-key"; var key = "some-key";
var actionDescriptor = new ActionDescriptor var actionDescriptor = new ActionDescriptor();
{ actionDescriptor.RouteValues.Add(key, null);
RouteConstraints = new[]
{
new RouteDataActionConstraint(key, routeValue: string.Empty)
}
};
var actionContext = new ActionContext var actionContext = new ActionContext
{ {
@ -1575,7 +1560,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
var result = RazorViewEngine.GetNormalizedRouteValue(actionContext, key); var result = RazorViewEngine.GetNormalizedRouteValue(actionContext, key);
// Assert // Assert
Assert.Null(result); Assert.Equal("route-value", result);
} }
[Fact] [Fact]
@ -1762,13 +1747,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
} }
var actionDesciptor = new ActionDescriptor(); var actionDesciptor = new ActionDescriptor();
actionDesciptor.RouteConstraints = new List<RouteDataActionConstraint>();
return new ActionContext(httpContext, routeData, actionDesciptor); return new ActionContext(httpContext, routeData, actionDesciptor);
} }
private static ActionContext GetActionContextWithActionDescriptor( private static ActionContext GetActionContextWithActionDescriptor(
IDictionary<string, object> routeValues, IDictionary<string, object> routeValues,
IDictionary<string, string> routesInActionDescriptor, IDictionary<string, string> actionRouteValues,
bool isAttributeRouted) bool isAttributeRouted)
{ {
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
@ -1782,17 +1766,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
if (isAttributeRouted) if (isAttributeRouted)
{ {
actionDescriptor.AttributeRouteInfo = new AttributeRouteInfo(); actionDescriptor.AttributeRouteInfo = new AttributeRouteInfo();
foreach (var kvp in routesInActionDescriptor) foreach (var kvp in actionRouteValues)
{ {
actionDescriptor.RouteValueDefaults.Add(kvp.Key, kvp.Value); actionDescriptor.RouteValueDefaults.Add(kvp.Key, kvp.Value);
} }
} }
else else
{ {
actionDescriptor.RouteConstraints = new List<RouteDataActionConstraint>(); foreach (var kvp in actionRouteValues)
foreach (var kvp in routesInActionDescriptor)
{ {
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(kvp.Key, kvp.Value)); actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value);
} }
} }

View File

@ -84,14 +84,14 @@ namespace System.Web.Http
var action = Assert.Single( var action = Assert.Single(
actions, actions,
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == "GetAll")); a => a.RouteValues.Any(rc => rc.Key == "action" && rc.Value == "GetAll"));
Assert.Equal( Assert.Equal(
new string[] { "GET" }, new string[] { "GET" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods); Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
action = Assert.Single( action = Assert.Single(
actions, actions,
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == "")); a => a.RouteValues.Any(rc => rc.Key == "action" && string.IsNullOrEmpty(rc.Value)));
Assert.Equal( Assert.Equal(
new string[] { "GET" }, new string[] { "GET" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods); Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
@ -120,14 +120,14 @@ namespace System.Web.Http
var action = Assert.Single( var action = Assert.Single(
actions, actions,
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == "Edit")); a => a.RouteValues.Any(rc => rc.Key == "action" && rc.Value == "Edit"));
Assert.Equal( Assert.Equal(
new string[] { "POST" }, new string[] { "POST" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods); Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
action = Assert.Single( action = Assert.Single(
actions, actions,
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == "")); a => a.RouteValues.Any(rc => rc.Key == "action" && string.IsNullOrEmpty(rc.Value)));
Assert.Equal( Assert.Equal(
new string[] { "POST" }, new string[] { "POST" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods); Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
@ -156,14 +156,14 @@ namespace System.Web.Http
var action = Assert.Single( var action = Assert.Single(
actions, actions,
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == "Delete")); a => a.RouteValues.Any(rc => rc.Key == "action" && rc.Value == "Delete"));
Assert.Equal( Assert.Equal(
new string[] { "PUT" }, new string[] { "PUT" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods); Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
action = Assert.Single( action = Assert.Single(
actions, actions,
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == "")); a => a.RouteValues.Any(rc => rc.Key == "action" && string.IsNullOrEmpty(rc.Value)));
Assert.Equal( Assert.Equal(
new string[] { "PUT" }, new string[] { "PUT" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods); Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
@ -193,14 +193,14 @@ namespace System.Web.Http
var action = Assert.Single( var action = Assert.Single(
actions, actions,
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == "GetOptions")); a => a.RouteValues.Any(rc => rc.Key == "action" && rc.Value == "GetOptions"));
Assert.Equal( Assert.Equal(
new string[] { "OPTIONS" }, new string[] { "OPTIONS" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods); Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
action = Assert.Single( action = Assert.Single(
actions, actions,
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == "")); a => a.RouteValues.Any(rc => rc.Key == "action" && string.IsNullOrEmpty(rc.Value)));
Assert.Equal( Assert.Equal(
new string[] { "OPTIONS" }, new string[] { "OPTIONS" },
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods); Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
@ -252,7 +252,7 @@ namespace System.Web.Http
Assert.NotEmpty(actions); Assert.NotEmpty(actions);
foreach (var action in actions) foreach (var action in actions)
{ {
Assert.Single(action.RouteConstraints, c => c.RouteKey == "area" && c.RouteValue == "api"); Assert.Single(action.RouteValues, c => c.Key == "area" && c.Value== "api");
} }
} }