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 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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
/// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
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>()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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 },
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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() { }
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue