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:
parent
74a74fb3a8
commit
af58c2e6b6
|
|
@ -3,15 +3,14 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
|
||||
namespace ActionConstraintSample.Web
|
||||
{
|
||||
public class CountrySpecificAttribute : RouteConstraintAttribute, IActionConstraint
|
||||
public class CountrySpecificAttribute : Attribute, IActionConstraint
|
||||
{
|
||||
private readonly string _countryCode;
|
||||
|
||||
public CountrySpecificAttribute(string countryCode)
|
||||
: base("country", countryCode, blockNonAttributedActions: false)
|
||||
{
|
||||
_countryCode = countryCode;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ using Microsoft.AspNetCore.Mvc.Routing;
|
|||
|
||||
namespace CustomRouteSample.Web
|
||||
{
|
||||
public class LocaleAttribute : RouteConstraintAttribute
|
||||
public class LocaleAttribute : RouteValueAttribute
|
||||
{
|
||||
public LocaleAttribute(string locale)
|
||||
: base("locale", routeValue: locale, blockNonAttributedActions: true)
|
||||
: base("locale", routeValue: locale)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ using Microsoft.AspNetCore.Mvc.Routing;
|
|||
namespace MvcSubAreaSample.Web
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class SubAreaAttribute : RouteConstraintAttribute
|
||||
public class SubAreaAttribute : RouteValueAttribute
|
||||
{
|
||||
public SubAreaAttribute(string name)
|
||||
: base("subarea", name, blockNonAttributedActions: true)
|
||||
: base("subarea", name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,12 +29,11 @@ namespace MvcSubAreaSample.Web
|
|||
|
||||
public void PopulateValues(ViewLocationExpanderContext context)
|
||||
{
|
||||
var subArea = context.ActionContext.ActionDescriptor.RouteConstraints.FirstOrDefault(
|
||||
s => s.RouteKey == "subarea" && !string.IsNullOrEmpty(s.RouteValue));
|
||||
|
||||
if (subArea != null)
|
||||
string subArea;
|
||||
if (context.ActionContext.ActionDescriptor.RouteValues.TryGetValue(_subAreaKey, out subArea) &&
|
||||
!string.IsNullOrEmpty(subArea))
|
||||
{
|
||||
context.Values[_subAreaKey] = subArea.RouteValue;
|
||||
context.Values[_subAreaKey] = subArea;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,10 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions
|
|||
{
|
||||
public ActionDescriptor()
|
||||
{
|
||||
Properties = new Dictionary<object, object>();
|
||||
RouteValueDefaults = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
Id = Guid.NewGuid().ToString();
|
||||
Properties = new Dictionary<object, object>();
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
RouteValueDefaults = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -25,7 +26,11 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions
|
|||
|
||||
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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,9 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
{
|
||||
|
|
@ -34,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
Attributes = new List<object>(attributes);
|
||||
Filters = new List<IFilterMetadata>();
|
||||
Parameters = new List<ParameterModel>();
|
||||
RouteConstraints = new List<IRouteConstraintProvider>();
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
Properties = new Dictionary<object, object>();
|
||||
Selectors = new List<SelectorModel>();
|
||||
}
|
||||
|
|
@ -56,11 +57,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
Attributes = new List<object>(other.Attributes);
|
||||
Filters = new List<IFilterMetadata>(other.Filters);
|
||||
Properties = new Dictionary<object, object>(other.Properties);
|
||||
RouteValues = new Dictionary<string, string>(other.RouteValues, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Make a deep copy of other 'model' types.
|
||||
ApiExplorer = new ApiExplorerModel(other.ApiExplorer);
|
||||
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)));
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +89,24 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
|
||||
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>
|
||||
/// Gets a set of properties associated with the action.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Linq;
|
|||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
{
|
||||
|
|
@ -36,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
ControllerProperties = new List<PropertyModel>();
|
||||
Filters = new List<IFilterMetadata>();
|
||||
Properties = new Dictionary<object, object>();
|
||||
RouteConstraints = new List<IRouteConstraintProvider>();
|
||||
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
Selectors = new List<SelectorModel>();
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
// These are just metadata, safe to create new collections
|
||||
Attributes = new List<object>(other.Attributes);
|
||||
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);
|
||||
|
||||
// Make a deep copy of other 'model' types.
|
||||
|
|
@ -97,7 +98,15 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
|
||||
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>
|
||||
/// Gets a set of properties associated with the controller.
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ using Microsoft.AspNetCore.Mvc.Routing;
|
|||
namespace Microsoft.AspNetCore.Mvc
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class AreaAttribute : RouteConstraintAttribute
|
||||
public class AreaAttribute : RouteValueAttribute
|
||||
{
|
||||
public AreaAttribute(string areaName)
|
||||
: base("area", areaName, blockNonAttributedActions: true)
|
||||
: base("area", areaName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(areaName))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -83,36 +83,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
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;
|
||||
if (constraint.KeyHandling == RouteKeyHandling.DenyKey)
|
||||
{
|
||||
// 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);
|
||||
// null and string.Empty are equivalent for route values, so just treat nulls as
|
||||
// string.Empty.
|
||||
results.Add(kvp.Key, new DecisionCriterionValue(kvp.Value ?? string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,14 +48,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
var tree = _decisionTreeProvider.DecisionTree;
|
||||
var matchingRouteConstraints = tree.Select(context.RouteData.Values);
|
||||
var matchingRouteValues = tree.Select(context.RouteData.Values);
|
||||
|
||||
var candidates = new List<ActionSelectorCandidate>();
|
||||
|
||||
// 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);
|
||||
candidates.Add(new ActionSelectorCandidate(action, constraints));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,11 +179,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Dictionary<string, RouteTemplate> templateCache,
|
||||
ActionDescriptor action)
|
||||
{
|
||||
var constraint = action.RouteConstraints
|
||||
.FirstOrDefault(c => c.RouteKey == TreeRouter.RouteGroupKey);
|
||||
if (constraint == null ||
|
||||
constraint.KeyHandling != RouteKeyHandling.RequireKey ||
|
||||
constraint.RouteValue == null)
|
||||
string value;
|
||||
action.RouteValues.TryGetValue(TreeRouter.RouteGroupKey, out value);
|
||||
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
// 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
|
||||
|
|
@ -196,7 +195,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var routeInfo = new RouteInfo()
|
||||
{
|
||||
ActionDescriptor = action,
|
||||
RouteGroup = constraint.RouteValue,
|
||||
RouteGroup = value,
|
||||
};
|
||||
|
||||
try
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var actions = new List<ControllerActionDescriptor>();
|
||||
|
||||
var hasAttributeRoutes = false;
|
||||
var removalConstraints = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var routeValueKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var methodInfoMap = new MethodToActionMap();
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
actionDescriptor.ControllerTypeInfo = controller.ControllerType;
|
||||
|
||||
AddApiExplorerInfo(actionDescriptor, application, controller, action);
|
||||
AddRouteConstraints(removalConstraints, actionDescriptor, controller, action);
|
||||
AddRouteValues(routeValueKeys, actionDescriptor, controller, action);
|
||||
AddProperties(actionDescriptor, action, controller, application);
|
||||
|
||||
actionDescriptor.BoundProperties = controllerPropertyDescriptors;
|
||||
|
|
@ -77,15 +77,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// An attribute routed action will ignore conventional routed constraints. We still
|
||||
// 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
|
||||
// for this action.
|
||||
ReplaceAttributeRouteTokens(actionDescriptor, routeTemplateErrors);
|
||||
|
||||
// Attribute routed actions will ignore conventional routed constraints. Instead they have
|
||||
// a single route constraint "RouteGroup" associated with it.
|
||||
ReplaceRouteConstraints(actionDescriptor);
|
||||
// Attribute routed actions will ignore conventional routed values. Instead they have
|
||||
// a single route value "RouteGroup" associated with it.
|
||||
ReplaceRouteValues(actionDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,16 +119,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// selected when a route group returned by the route.
|
||||
if (hasAttributeRoutes)
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
TreeRouter.RouteGroupKey,
|
||||
string.Empty));
|
||||
actionDescriptor.RouteValues.Add(TreeRouter.RouteGroupKey, string.Empty);
|
||||
}
|
||||
|
||||
// Add a route constraint with DenyKey for each constraint in the set to all the
|
||||
// actions that don't have that constraint. For example, if a controller defines
|
||||
// an area constraint, all actions that don't belong to an area must have a route
|
||||
// constraint that prevents them from matching an incoming request.
|
||||
AddRemovalConstraints(actionDescriptor, removalConstraints);
|
||||
// Add a route value with 'null' for each user-defined route value in the set to all the
|
||||
// actions that don't have that value. For example, if a controller defines
|
||||
// an area, all actions that don't belong to an area must have a route
|
||||
// value that prevents them from matching an incoming request when area is specified.
|
||||
AddGlobalRouteValues(actionDescriptor, routeValueKeys);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -145,7 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
//
|
||||
// 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.
|
||||
foreach (var key in removalConstraints)
|
||||
foreach (var key in routeValueKeys)
|
||||
{
|
||||
if (!actionDescriptor.RouteValueDefaults.ContainsKey(key))
|
||||
{
|
||||
|
|
@ -287,7 +285,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Name = action.ActionName,
|
||||
MethodInfo = action.ActionMethod,
|
||||
Parameters = parameterDescriptors,
|
||||
RouteConstraints = new List<RouteDataActionConstraint>(),
|
||||
AttributeRouteInfo = CreateAttributeRouteInfo(actionAttributeRoute, controllerAttributeRoute)
|
||||
};
|
||||
|
||||
|
|
@ -448,8 +445,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
}
|
||||
|
||||
public static void AddRouteConstraints(
|
||||
ISet<string> removalConstraints,
|
||||
public static void AddRouteValues(
|
||||
ISet<string> keys,
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
ControllerModel controller,
|
||||
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
|
||||
// controller should not match when a value has been given for area when matching a url or
|
||||
// generating a link.
|
||||
foreach (var constraintAttribute in action.RouteConstraints)
|
||||
foreach (var kvp in action.RouteValues)
|
||||
{
|
||||
if (constraintAttribute.BlockNonAttributedActions)
|
||||
{
|
||||
removalConstraints.Add(constraintAttribute.RouteKey);
|
||||
}
|
||||
|
||||
keys.Add(kvp.Key);
|
||||
|
||||
// Skip duplicates
|
||||
if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey))
|
||||
if (!actionDescriptor.RouteValues.ContainsKey(kvp.Key))
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
constraintAttribute.RouteKey,
|
||||
constraintAttribute.RouteValue));
|
||||
actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var constraintAttribute in controller.RouteConstraints)
|
||||
foreach (var kvp in controller.RouteValues)
|
||||
{
|
||||
if (constraintAttribute.BlockNonAttributedActions)
|
||||
{
|
||||
removalConstraints.Add(constraintAttribute.RouteKey);
|
||||
}
|
||||
keys.Add(kvp.Key);
|
||||
|
||||
// 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(
|
||||
constraintAttribute.RouteKey,
|
||||
constraintAttribute.RouteValue));
|
||||
actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly add the 'default' values
|
||||
if (!HasConstraint(actionDescriptor.RouteConstraints, "action"))
|
||||
if (!actionDescriptor.RouteValues.ContainsKey("action"))
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
"action",
|
||||
action.ActionName ?? string.Empty));
|
||||
actionDescriptor.RouteValues.Add("action", action.ActionName ?? string.Empty);
|
||||
}
|
||||
|
||||
if (!HasConstraint(actionDescriptor.RouteConstraints, "controller"))
|
||||
if (!actionDescriptor.RouteValues.ContainsKey("controller"))
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
"controller",
|
||||
controller.ControllerName));
|
||||
actionDescriptor.RouteValues.Add("controller", controller.ControllerName);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasConstraint(IList<RouteDataActionConstraint> constraints, string routeKey)
|
||||
{
|
||||
return constraints.Any(
|
||||
rc => string.Equals(rc.RouteKey, routeKey, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static void ReplaceRouteConstraints(ControllerActionDescriptor actionDescriptor)
|
||||
private static void ReplaceRouteValues(ControllerActionDescriptor actionDescriptor)
|
||||
{
|
||||
var routeGroupValue = GetRouteGroupValue(
|
||||
actionDescriptor.AttributeRouteInfo.Order,
|
||||
actionDescriptor.AttributeRouteInfo.Template);
|
||||
|
||||
var routeConstraints = new List<RouteDataActionConstraint>();
|
||||
routeConstraints.Add(new RouteDataActionConstraint(
|
||||
TreeRouter.RouteGroupKey,
|
||||
routeGroupValue));
|
||||
|
||||
actionDescriptor.RouteConstraints = routeConstraints;
|
||||
actionDescriptor.RouteValues.Clear();
|
||||
actionDescriptor.RouteValues.Add(TreeRouter.RouteGroupKey, routeGroupValue);
|
||||
}
|
||||
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
actionDescriptor.RouteValueDefaults.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddRemovalConstraints(
|
||||
private static void AddGlobalRouteValues(
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
ISet<string> removalConstraints)
|
||||
{
|
||||
foreach (var key in removalConstraints)
|
||||
{
|
||||
if (!HasConstraint(actionDescriptor.RouteConstraints, key))
|
||||
if (!actionDescriptor.RouteValues.ContainsKey(key))
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
key,
|
||||
string.Empty));
|
||||
actionDescriptor.RouteValues.Add(key, string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,7 +170,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
typeInfo.Name;
|
||||
|
||||
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();
|
||||
if (apiVisibility != null)
|
||||
|
|
@ -285,8 +289,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
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
|
||||
// Now we need to determine the action selection info (cross-section of routes and constraints)
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
|
|
@ -71,18 +72,20 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
if (valuesCollection == null ||
|
||||
version != valuesCollection.Version)
|
||||
{
|
||||
var routeValueCollection =
|
||||
actionDescriptors
|
||||
.Items
|
||||
.Select(ad => ad.RouteConstraints.FirstOrDefault(
|
||||
c => c.RouteKey == routeKey &&
|
||||
c.KeyHandling == RouteKeyHandling.RequireKey))
|
||||
.Where(rc => rc != null)
|
||||
.Select(rc => rc.RouteValue)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
var values = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
for (var i = 0; i < actionDescriptors.Items.Count; i++)
|
||||
{
|
||||
var action = actionDescriptors.Items[i];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
/// 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.RouteConstraints"/> for traditional routes to get route values
|
||||
/// <see cref="Abstractions.ActionDescriptor.RouteValues"/> for traditional routes to get route values
|
||||
/// produces consistently cased results.
|
||||
/// </remarks>
|
||||
public static string GetNormalizedRouteValue(ActionContext context, string key)
|
||||
|
|
@ -149,24 +149,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
}
|
||||
else
|
||||
{
|
||||
// Perf: Avoid allocations
|
||||
for (var i = 0; i < actionDescriptor.RouteConstraints.Count; i++)
|
||||
string value;
|
||||
if (actionDescriptor.RouteValues.TryGetValue(key, out value) &&
|
||||
!string.IsNullOrEmpty(value))
|
||||
{
|
||||
var constraint = actionDescriptor.RouteConstraints[i];
|
||||
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;
|
||||
}
|
||||
normalizedValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
|
|||
var namedAction = action;
|
||||
|
||||
var unnamedAction = new ActionModel(namedAction);
|
||||
unnamedAction.RouteConstraints.Add(new UnnamedActionRouteConstraint());
|
||||
unnamedAction.RouteValues.Add("action", null);
|
||||
newActions.Add(unnamedAction);
|
||||
}
|
||||
}
|
||||
|
|
@ -114,23 +114,5 @@ namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
|
|||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
|
|||
|
||||
if (IsConventionApplicable(controller))
|
||||
{
|
||||
controller.RouteConstraints.Add(new AreaAttribute(_area));
|
||||
controller.RouteValues.Add("area", _area);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,10 +64,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
action.Selectors.Add(selectorModel);
|
||||
action.ActionName = "Edit";
|
||||
|
||||
action.Controller = new ControllerModel(typeof(TestController).GetTypeInfo(),
|
||||
new List<object>());
|
||||
action.Controller = new ControllerModel
|
||||
(typeof(TestController).GetTypeInfo(),
|
||||
new List<object>());
|
||||
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"));
|
||||
|
||||
// Act
|
||||
|
|
@ -95,6 +96,13 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
// Ensure non-default value
|
||||
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))
|
||||
{
|
||||
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 RouteKeyHandling RouteKeyHandling { get { return RouteKeyHandling.RequireKey; } }
|
||||
|
||||
public string RouteValue { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
Assert.NotSame(controller.Actions, controller2.Actions);
|
||||
Assert.NotSame(controller.Attributes, controller2.Attributes);
|
||||
Assert.NotSame(controller.Filters, controller2.Filters);
|
||||
Assert.NotSame(controller.RouteConstraints, controller2.RouteConstraints);
|
||||
Assert.NotSame(controller.RouteValues, controller2.RouteValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
controller.Application = new ApplicationModel();
|
||||
controller.ControllerName = "cool";
|
||||
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.ControllerProperties.Add(
|
||||
new PropertyModel(typeof(TestController).GetProperty("TestProperty"), new List<object>()));
|
||||
|
|
@ -99,6 +99,13 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
// Ensure non-default value
|
||||
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))
|
||||
{
|
||||
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 RouteKeyHandling RouteKeyHandling { get { return RouteKeyHandling.RequireKey; } }
|
||||
|
||||
public string RouteValue { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -600,9 +600,9 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
|
||||
return
|
||||
actions
|
||||
.Where(a => a.RouteConstraints.Any(c => c.RouteKey == "area" && comparer.Equals(c.RouteValue, area)))
|
||||
.Where(a => a.RouteConstraints.Any(c => c.RouteKey == "controller" && comparer.Equals(c.RouteValue, controller)))
|
||||
.Where(a => a.RouteConstraints.Any(c => c.RouteKey == "action" && comparer.Equals(c.RouteValue, action)));
|
||||
.Where(a => a.RouteValues.Any(kvp => kvp.Key == "area" && comparer.Equals(kvp.Value, area)))
|
||||
.Where(a => a.RouteValues.Any(kvp => kvp.Key == "controller" && comparer.Equals(kvp.Value, controller)))
|
||||
.Where(a => a.RouteValues.Any(kvp => kvp.Key == "action" && comparer.Equals(kvp.Value, action)));
|
||||
}
|
||||
|
||||
private static ActionSelector CreateSelector(IReadOnlyList<ActionDescriptor> actions, ILoggerFactory loggerFactory = null)
|
||||
|
|
@ -667,24 +667,12 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
|||
var actionDescriptor = new ActionDescriptor()
|
||||
{
|
||||
Name = string.Format("Area: {0}, Controller: {1}, Action: {2}", area, controller, action),
|
||||
RouteConstraints = new List<RouteDataActionConstraint>(),
|
||||
Parameters = new List<ParameterDescriptor>(),
|
||||
};
|
||||
|
||||
actionDescriptor.RouteConstraints.Add(
|
||||
area == null ?
|
||||
new RouteDataActionConstraint("area", null) :
|
||||
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));
|
||||
actionDescriptor.RouteValues.Add("area", area);
|
||||
actionDescriptor.RouteValues.Add("controller", controller);
|
||||
actionDescriptor.RouteValues.Add("action", action);
|
||||
|
||||
return actionDescriptor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,9 +42,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
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()
|
||||
|
|
@ -53,9 +53,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
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",
|
||||
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>()
|
||||
{
|
||||
|
|
@ -164,9 +164,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Name = "BLOG_INDEX",
|
||||
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>()
|
||||
{
|
||||
|
|
@ -212,9 +212,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Name = "BLOG_INDEX",
|
||||
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>()
|
||||
{
|
||||
|
|
@ -263,9 +263,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Name = "BLOG_INDEX",
|
||||
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>()
|
||||
{
|
||||
|
|
@ -281,9 +281,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Name = "BLOG_INDEX",
|
||||
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>()
|
||||
{
|
||||
|
|
@ -339,9 +339,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Name = "BLOG_INDEX",
|
||||
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>()
|
||||
{
|
||||
|
|
@ -388,9 +388,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Name = "BLOG_INDEX",
|
||||
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>()
|
||||
{
|
||||
|
|
@ -437,9 +437,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Name = "BLOG_INDEX",
|
||||
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>()
|
||||
{
|
||||
|
|
@ -490,9 +490,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Name = "BLOG_INDEX",
|
||||
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>()
|
||||
{
|
||||
|
|
@ -508,9 +508,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Name = "BLOG_INDEX",
|
||||
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>()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -131,9 +131,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var action = new ControllerActionDescriptor();
|
||||
action.DisplayName = "Microsoft.AspNetCore.Mvc.Routing.AttributeRoutingTest+HomeController.Index";
|
||||
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.Template = "{controller}/{action}";
|
||||
|
|
@ -167,9 +167,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
return new DisplayNameActionDescriptor()
|
||||
{
|
||||
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 },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -242,17 +242,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// Assert
|
||||
var action = Assert.Single(descriptors);
|
||||
|
||||
Assert.NotNull(action.RouteConstraints);
|
||||
Assert.NotNull(action.RouteValues);
|
||||
|
||||
var controller = Assert.Single(action.RouteConstraints,
|
||||
rc => rc.RouteKey.Equals("controller"));
|
||||
Assert.Equal(RouteKeyHandling.RequireKey, controller.KeyHandling);
|
||||
Assert.Equal("ConventionallyRouted", controller.RouteValue);
|
||||
var controller = Assert.Single(action.RouteValues, kvp => kvp.Key.Equals("controller"));
|
||||
Assert.Equal("ConventionallyRouted", controller.Value);
|
||||
|
||||
var actionConstraint = Assert.Single(action.RouteConstraints,
|
||||
rc => rc.RouteKey.Equals("action"));
|
||||
Assert.Equal(RouteKeyHandling.RequireKey, actionConstraint.KeyHandling);
|
||||
Assert.Equal(nameof(ConventionallyRoutedController.ConventionalAction), actionConstraint.RouteValue);
|
||||
var actionConstraint = Assert.Single(action.RouteValues, kvp => kvp.Key.Equals("action"));
|
||||
Assert.Equal(nameof(ConventionallyRoutedController.ConventionalAction), actionConstraint.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -265,132 +261,67 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// Assert
|
||||
var action = Assert.Single(descriptors);
|
||||
|
||||
var routeconstraint = Assert.Single(action.RouteConstraints);
|
||||
Assert.Equal(RouteKeyHandling.RequireKey, routeconstraint.KeyHandling);
|
||||
Assert.Equal(TreeRouter.RouteGroupKey, routeconstraint.RouteKey);
|
||||
Assert.Equal(TreeRouter.RouteGroupKey, Assert.Single(action.RouteValues).Key);
|
||||
|
||||
var controller = Assert.Single(action.RouteValueDefaults,
|
||||
rc => rc.Key.Equals("controller"));
|
||||
var controller = Assert.Single(action.RouteValueDefaults, kvp => kvp.Key.Equals("controller"));
|
||||
Assert.Equal("AttributeRouted", controller.Value);
|
||||
|
||||
var actionConstraint = Assert.Single(action.RouteValueDefaults,
|
||||
rc => rc.Key.Equals("action"));
|
||||
var actionConstraint = Assert.Single(action.RouteValueDefaults, kvp => kvp.Key.Equals("action"));
|
||||
Assert.Equal(nameof(AttributeRoutedController.AttributeRoutedAction), actionConstraint.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_WithRouteDataConstraint_WithBlockNonAttributedActions()
|
||||
public void GetDescriptors_WithRouteValueAttribute()
|
||||
{
|
||||
// Arrange & Act
|
||||
var descriptors = GetDescriptors(
|
||||
typeof(HttpMethodController).GetTypeInfo(),
|
||||
typeof(BlockNonAttributedActionsController).GetTypeInfo()).ToArray();
|
||||
typeof(RouteValueController).GetTypeInfo()).ToArray();
|
||||
|
||||
var descriptorWithoutConstraint = Assert.Single(
|
||||
var descriptorWithoutValue = Assert.Single(
|
||||
descriptors,
|
||||
ad => ad.RouteConstraints.Any(
|
||||
c => c.RouteKey == "key" && c.KeyHandling == RouteKeyHandling.DenyKey));
|
||||
ad => ad.RouteValues.Any(kvp => kvp.Key == "key" && string.IsNullOrEmpty(kvp.Value)));
|
||||
|
||||
var descriptorWithConstraint = Assert.Single(
|
||||
var descriptorWithValue = Assert.Single(
|
||||
descriptors,
|
||||
ad => ad.RouteConstraints.Any(
|
||||
c =>
|
||||
c.KeyHandling == RouteKeyHandling.RequireKey &&
|
||||
c.RouteKey == "key" &&
|
||||
c.RouteValue == "value"));
|
||||
ad => ad.RouteValues.Any(kvp => kvp.Key == "key" && kvp.Value == "value"));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, descriptors.Length);
|
||||
|
||||
Assert.Equal(3, descriptorWithConstraint.RouteConstraints.Count);
|
||||
Assert.Equal(3, descriptorWithValue.RouteValues.Count);
|
||||
Assert.Single(
|
||||
descriptorWithConstraint.RouteConstraints,
|
||||
descriptorWithValue.RouteValues,
|
||||
c =>
|
||||
c.RouteKey == "controller" &&
|
||||
c.RouteValue == "BlockNonAttributedActions");
|
||||
c.Key == "controller" &&
|
||||
c.Value == "RouteValue");
|
||||
Assert.Single(
|
||||
descriptorWithConstraint.RouteConstraints,
|
||||
descriptorWithValue.RouteValues,
|
||||
c =>
|
||||
c.RouteKey == "action" &&
|
||||
c.RouteValue == "Edit");
|
||||
c.Key == "action" &&
|
||||
c.Value == "Edit");
|
||||
Assert.Single(
|
||||
descriptorWithConstraint.RouteConstraints,
|
||||
descriptorWithValue.RouteValues,
|
||||
c =>
|
||||
c.RouteKey == "key" &&
|
||||
c.RouteValue == "value" &&
|
||||
c.KeyHandling == RouteKeyHandling.RequireKey);
|
||||
c.Key == "key" &&
|
||||
c.Value == "value");
|
||||
|
||||
Assert.Equal(3, descriptorWithoutConstraint.RouteConstraints.Count);
|
||||
Assert.Equal(3, descriptorWithoutValue.RouteValues.Count);
|
||||
Assert.Single(
|
||||
descriptorWithoutConstraint.RouteConstraints,
|
||||
descriptorWithoutValue.RouteValues,
|
||||
c =>
|
||||
c.RouteKey == "controller" &&
|
||||
c.RouteValue == "HttpMethod");
|
||||
c.Key == "controller" &&
|
||||
c.Value == "HttpMethod");
|
||||
Assert.Single(
|
||||
descriptorWithoutConstraint.RouteConstraints,
|
||||
descriptorWithoutValue.RouteValues,
|
||||
c =>
|
||||
c.RouteKey == "action" &&
|
||||
c.RouteValue == "OnlyPost");
|
||||
c.Key == "action" &&
|
||||
c.Value == "OnlyPost");
|
||||
Assert.Single(
|
||||
descriptorWithoutConstraint.RouteConstraints,
|
||||
descriptorWithoutValue.RouteValues,
|
||||
c =>
|
||||
c.RouteKey == "key" &&
|
||||
c.RouteValue == 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");
|
||||
c.Key == "key" &&
|
||||
c.Value == string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1002,10 +933,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
foreach (var actionDescriptor in actionDescriptors.Where(ad => ad.AttributeRouteInfo == null))
|
||||
{
|
||||
Assert.Equal(6, actionDescriptor.RouteConstraints.Count);
|
||||
var routeGroupConstraint = Assert.Single(actionDescriptor.RouteConstraints,
|
||||
rc => rc.RouteKey.Equals(TreeRouter.RouteGroupKey));
|
||||
Assert.Equal(RouteKeyHandling.DenyKey, routeGroupConstraint.KeyHandling);
|
||||
Assert.Equal(6, actionDescriptor.RouteValues.Count);
|
||||
Assert.Single(
|
||||
actionDescriptor.RouteValues,
|
||||
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"));
|
||||
|
||||
Assert.Equal(1, indexAction.RouteConstraints.Count);
|
||||
Assert.Equal(1, indexAction.RouteValues.Count);
|
||||
|
||||
var routeGroupConstraint = Assert.Single(indexAction.RouteConstraints, rc => rc.RouteKey.Equals(TreeRouter.RouteGroupKey));
|
||||
Assert.Equal(RouteKeyHandling.RequireKey, routeGroupConstraint.KeyHandling);
|
||||
Assert.NotNull(routeGroupConstraint.RouteValue);
|
||||
var routeGroup = Assert.Single(indexAction.RouteValues, kvp => kvp.Key.Equals(TreeRouter.RouteGroupKey));
|
||||
Assert.NotNull(routeGroup.Value);
|
||||
|
||||
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));
|
||||
Assert.Equal("Home", areaDefault.Value);
|
||||
|
||||
var myRouteConstraintDefault = Assert.Single(indexAction.RouteValueDefaults, rd => rd.Key.Equals("key", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Null(myRouteConstraintDefault.Value);
|
||||
var mvRouteValueDefault = Assert.Single(indexAction.RouteValueDefaults, rd => rd.Key.Equals("key", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Null(mvRouteValueDefault.Value);
|
||||
|
||||
var anotherRouteConstraint = Assert.Single(indexAction.RouteValueDefaults, rd => rd.Key.Equals("second", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Null(anotherRouteConstraint.Value);
|
||||
var anotherRouteValue = Assert.Single(indexAction.RouteValueDefaults, rd => rd.Key.Equals("second", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Null(anotherRouteValue.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1076,9 +1006,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var actions = provider.GetDescriptors().ToArray();
|
||||
|
||||
var groupIds = actions.Select(
|
||||
a => a.RouteConstraints
|
||||
.Where(rc => rc.RouteKey == TreeRouter.RouteGroupKey)
|
||||
.Select(rc => rc.RouteValue)
|
||||
a => a.RouteValues
|
||||
.Where(kvp => kvp.Key == TreeRouter.RouteGroupKey)
|
||||
.Select(kvp => kvp.Value)
|
||||
.Single())
|
||||
.ToArray();
|
||||
|
||||
|
|
@ -1625,38 +1555,30 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{ }
|
||||
}
|
||||
|
||||
private class MyRouteConstraintAttribute : RouteConstraintAttribute
|
||||
private class MyRouteValueAttribute : RouteValueAttribute
|
||||
{
|
||||
public MyRouteConstraintAttribute(bool blockNonAttributedActions)
|
||||
: base("key", "value", blockNonAttributedActions)
|
||||
public MyRouteValueAttribute()
|
||||
: base("key", "value")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class MySecondRouteConstraintAttribute : RouteConstraintAttribute
|
||||
private class MySecondRouteValueAttribute : RouteValueAttribute
|
||||
{
|
||||
public MySecondRouteConstraintAttribute(bool blockNonAttributedActions)
|
||||
: base("second", "value", blockNonAttributedActions)
|
||||
public MySecondRouteValueAttribute()
|
||||
: base("second", "value")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[MyRouteConstraint(blockNonAttributedActions: true)]
|
||||
private class BlockNonAttributedActionsController
|
||||
[MyRouteValue]
|
||||
private class RouteValueController
|
||||
{
|
||||
public void Edit()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[MyRouteConstraint(blockNonAttributedActions: false)]
|
||||
private class DontBlockNonAttributedActionsController
|
||||
{
|
||||
public void Create()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class MyFilterAttribute : Attribute, IFilterMetadata
|
||||
{
|
||||
public MyFilterAttribute(int value)
|
||||
|
|
@ -1677,7 +1599,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
[Route("api/Token/[key]/[controller]")]
|
||||
[MyRouteConstraint(false)]
|
||||
[MyRouteValue]
|
||||
private class TokenReplacementController
|
||||
{
|
||||
[HttpGet("stub/[action]")]
|
||||
|
|
@ -1880,8 +1802,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
public void DifferentHttpMethods() { }
|
||||
}
|
||||
|
||||
[MyRouteConstraint(blockNonAttributedActions: true)]
|
||||
[MySecondRouteConstraint(blockNonAttributedActions: true)]
|
||||
[MyRouteValue]
|
||||
[MySecondRouteValue]
|
||||
private class ConstrainedController
|
||||
{
|
||||
public void ConstrainedNonAttributedAction() { }
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
var actionDescriptor = CreateActionDescriptor("testArea",
|
||||
"testController",
|
||||
"testAction");
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint("randomKey", "testRandom"));
|
||||
actionDescriptor.RouteValues.Add("randomKey", "testRandom");
|
||||
var httpContext = GetHttpContext(actionDescriptor);
|
||||
var route = Mock.Of<IRouter>();
|
||||
var values = new RouteValueDictionary()
|
||||
|
|
@ -85,10 +85,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
public void RouteValue_DoesNotExists_MatchFails(string keyName, RouteDirection direction)
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = CreateActionDescriptor("testArea",
|
||||
"testController",
|
||||
"testAction");
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint("randomKey", "testRandom"));
|
||||
var actionDescriptor = CreateActionDescriptor(
|
||||
"testArea",
|
||||
"testController",
|
||||
"testAction");
|
||||
actionDescriptor.RouteValues.Add("randomKey", "testRandom");
|
||||
var httpContext = GetHttpContext(actionDescriptor);
|
||||
var route = Mock.Of<IRouter>();
|
||||
var values = new RouteValueDictionary()
|
||||
|
|
@ -186,23 +187,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
var actionDescriptor = new ActionDescriptor()
|
||||
{
|
||||
Name = string.Format("Area: {0}, Controller: {1}, Action: {2}", area, controller, action),
|
||||
RouteConstraints = new List<RouteDataActionConstraint>(),
|
||||
};
|
||||
|
||||
actionDescriptor.RouteConstraints.Add(
|
||||
area == null ?
|
||||
new RouteDataActionConstraint("area", null) :
|
||||
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));
|
||||
actionDescriptor.RouteValues.Add("area", area);
|
||||
actionDescriptor.RouteValues.Add("controller", controller);
|
||||
actionDescriptor.RouteValues.Add("action", action);
|
||||
|
||||
return actionDescriptor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1152,7 +1152,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
|||
[Theory]
|
||||
// Looks in RouteValueDefaults
|
||||
[InlineData(true)]
|
||||
// Looks in RouteConstraints
|
||||
// Looks in RouteValues
|
||||
[InlineData(false)]
|
||||
public void FindPage_SelectsActionCaseInsensitively(bool isAttributeRouted)
|
||||
{
|
||||
|
|
@ -1194,7 +1194,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
|||
[Theory]
|
||||
// Looks in RouteValueDefaults
|
||||
[InlineData(true)]
|
||||
// Looks in RouteConstraints
|
||||
// Looks in RouteValues
|
||||
[InlineData(false)]
|
||||
public void FindPage_LooksForPages_UsingActionDescriptor_Controller(bool isAttributeRouted)
|
||||
{
|
||||
|
|
@ -1232,7 +1232,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
|||
[Theory]
|
||||
// Looks in RouteValueDefaults
|
||||
[InlineData(true)]
|
||||
// Looks in RouteConstraints
|
||||
// Looks in RouteValues
|
||||
[InlineData(false)]
|
||||
public void FindPage_LooksForPages_UsingActionDescriptor_Areas(bool isAttributeRouted)
|
||||
{
|
||||
|
|
@ -1495,17 +1495,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetNormalizedRouteValue_ReturnsValueFromRouteConstraints_IfKeyHandlingIsRequired()
|
||||
public void GetNormalizedRouteValue_ReturnsValueFromRouteValues_IfKeyHandlingIsRequired()
|
||||
{
|
||||
// Arrange
|
||||
var key = "some-key";
|
||||
var actionDescriptor = new ActionDescriptor
|
||||
{
|
||||
RouteConstraints = new[]
|
||||
{
|
||||
new RouteDataActionConstraint(key, "Route-Value")
|
||||
}
|
||||
};
|
||||
var actionDescriptor = new ActionDescriptor();
|
||||
actionDescriptor.RouteValues.Add(key, "Route-Value");
|
||||
|
||||
var actionContext = new ActionContext
|
||||
{
|
||||
|
|
@ -1523,17 +1518,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetNormalizedRouteValue_ReturnsRouteValue_IfValueDoesNotMatchRouteConstraint()
|
||||
public void GetNormalizedRouteValue_ReturnsRouteValue_IfValueDoesNotMatch()
|
||||
{
|
||||
// Arrange
|
||||
var key = "some-key";
|
||||
var actionDescriptor = new ActionDescriptor
|
||||
{
|
||||
RouteConstraints = new[]
|
||||
{
|
||||
new RouteDataActionConstraint(key, "different-value")
|
||||
}
|
||||
};
|
||||
var actionDescriptor = new ActionDescriptor();
|
||||
actionDescriptor.RouteValues.Add(key, "different-value");
|
||||
|
||||
var actionContext = new ActionContext
|
||||
{
|
||||
|
|
@ -1551,17 +1541,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetNormalizedRouteValue_ReturnsNull_IfRouteConstraintKeyHandlingIsDeny()
|
||||
public void GetNormalizedRouteValue_ReturnsNonNormalizedValue_IfActionRouteValueIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var key = "some-key";
|
||||
var actionDescriptor = new ActionDescriptor
|
||||
{
|
||||
RouteConstraints = new[]
|
||||
{
|
||||
new RouteDataActionConstraint(key, routeValue: string.Empty)
|
||||
}
|
||||
};
|
||||
var actionDescriptor = new ActionDescriptor();
|
||||
actionDescriptor.RouteValues.Add(key, null);
|
||||
|
||||
var actionContext = new ActionContext
|
||||
{
|
||||
|
|
@ -1575,7 +1560,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
|||
var result = RazorViewEngine.GetNormalizedRouteValue(actionContext, key);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
Assert.Equal("route-value", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1762,13 +1747,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
|||
}
|
||||
|
||||
var actionDesciptor = new ActionDescriptor();
|
||||
actionDesciptor.RouteConstraints = new List<RouteDataActionConstraint>();
|
||||
return new ActionContext(httpContext, routeData, actionDesciptor);
|
||||
}
|
||||
|
||||
private static ActionContext GetActionContextWithActionDescriptor(
|
||||
IDictionary<string, object> routeValues,
|
||||
IDictionary<string, string> routesInActionDescriptor,
|
||||
IDictionary<string, string> actionRouteValues,
|
||||
bool isAttributeRouted)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
|
|
@ -1782,17 +1766,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
|
|||
if (isAttributeRouted)
|
||||
{
|
||||
actionDescriptor.AttributeRouteInfo = new AttributeRouteInfo();
|
||||
foreach (var kvp in routesInActionDescriptor)
|
||||
foreach (var kvp in actionRouteValues)
|
||||
{
|
||||
actionDescriptor.RouteValueDefaults.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
actionDescriptor.RouteConstraints = new List<RouteDataActionConstraint>();
|
||||
foreach (var kvp in routesInActionDescriptor)
|
||||
foreach (var kvp in actionRouteValues)
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(kvp.Key, kvp.Value));
|
||||
actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,14 +84,14 @@ namespace System.Web.Http
|
|||
|
||||
var action = Assert.Single(
|
||||
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(
|
||||
new string[] { "GET" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
|
||||
|
||||
action = Assert.Single(
|
||||
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(
|
||||
new string[] { "GET" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
|
||||
|
|
@ -120,14 +120,14 @@ namespace System.Web.Http
|
|||
|
||||
var action = Assert.Single(
|
||||
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(
|
||||
new string[] { "POST" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
|
||||
|
||||
action = Assert.Single(
|
||||
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(
|
||||
new string[] { "POST" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
|
||||
|
|
@ -156,14 +156,14 @@ namespace System.Web.Http
|
|||
|
||||
var action = Assert.Single(
|
||||
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(
|
||||
new string[] { "PUT" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
|
||||
|
||||
action = Assert.Single(
|
||||
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(
|
||||
new string[] { "PUT" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
|
||||
|
|
@ -193,14 +193,14 @@ namespace System.Web.Http
|
|||
|
||||
var action = Assert.Single(
|
||||
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(
|
||||
new string[] { "OPTIONS" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
|
||||
|
||||
action = Assert.Single(
|
||||
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(
|
||||
new string[] { "OPTIONS" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodActionConstraint>()).HttpMethods);
|
||||
|
|
@ -252,7 +252,7 @@ namespace System.Web.Http
|
|||
Assert.NotEmpty(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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue