Adding IRouteConstraintProvider and supporting it on actions
This change adds an interface for the functionality provide by RouteConstraintAttribute, and adds support for configuration constraints on actions/action-model.
This commit is contained in:
parent
5dfd27e51f
commit
ae9fc793ec
|
|
@ -20,6 +20,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
Filters = new List<IFilter>();
|
||||
HttpMethods = new List<string>();
|
||||
Parameters = new List<ParameterModel>();
|
||||
RouteConstraints = new List<IRouteConstraintProvider>();
|
||||
}
|
||||
|
||||
public ActionModel([NotNull] ActionModel other)
|
||||
|
|
@ -40,6 +41,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
// Make a deep copy of other 'model' types.
|
||||
ApiExplorer = new ApiExplorerModel(other.ApiExplorer);
|
||||
Parameters = new List<ParameterModel>(other.Parameters.Select(p => new ParameterModel(p)));
|
||||
RouteConstraints = new List<IRouteConstraintProvider>(other.RouteConstraints);
|
||||
|
||||
if (other.AttributeRouteModel != null)
|
||||
{
|
||||
|
|
@ -62,6 +64,8 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
/// </remarks>
|
||||
public ApiExplorerModel ApiExplorer { get; set; }
|
||||
|
||||
public AttributeRouteModel AttributeRouteModel { get; set; }
|
||||
|
||||
public IReadOnlyList<object> Attributes { get; }
|
||||
|
||||
public ControllerModel Controller { get; set; }
|
||||
|
|
@ -74,6 +78,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
|
||||
public List<ParameterModel> Parameters { get; private set; }
|
||||
|
||||
public AttributeRouteModel AttributeRouteModel { get; set; }
|
||||
public List<IRouteConstraintProvider> RouteConstraints { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
|
|
@ -336,7 +337,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
var message = Resources.FormatAttributeRoute_TokenReplacement_ReplacementValueNotFound(
|
||||
template,
|
||||
token,
|
||||
string.Join(", ", values.Keys));
|
||||
string.Join(", ", values.Keys.OrderBy(k => k, StringComparer.OrdinalIgnoreCase)));
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
AttributeRoutes = new List<AttributeRouteModel>();
|
||||
ActionConstraints = new List<IActionConstraintMetadata>();
|
||||
Filters = new List<IFilter>();
|
||||
RouteConstraints = new List<RouteConstraintAttribute>();
|
||||
RouteConstraints = new List<IRouteConstraintProvider>();
|
||||
}
|
||||
|
||||
public ControllerModel([NotNull] ControllerModel other)
|
||||
|
|
@ -35,7 +35,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
ActionConstraints = new List<IActionConstraintMetadata>(other.ActionConstraints);
|
||||
Attributes = new List<object>(other.Attributes);
|
||||
Filters = new List<IFilter>(other.Filters);
|
||||
RouteConstraints = new List<RouteConstraintAttribute>(other.RouteConstraints);
|
||||
RouteConstraints = new List<IRouteConstraintProvider>(other.RouteConstraints);
|
||||
|
||||
// Make a deep copy of other 'model' types.
|
||||
Actions = new List<ActionModel>(other.Actions.Select(a => new ActionModel(a)));
|
||||
|
|
@ -65,6 +65,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
|
||||
public List<IFilter> Filters { get; private set; }
|
||||
|
||||
public List<RouteConstraintAttribute> RouteConstraints { get; private set; }
|
||||
public List<IRouteConstraintProvider> RouteConstraints { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -289,6 +289,8 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
.SelectMany(a => a.HttpMethods)
|
||||
.Distinct());
|
||||
|
||||
actionModel.RouteConstraints.AddRange(attributes.OfType<IRouteConstraintProvider>());
|
||||
|
||||
var routeTemplateProvider =
|
||||
attributes
|
||||
.OfType<IRouteTemplateProvider>()
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
|
||||
controllerModel.ActionConstraints.AddRange(attributes.OfType<IActionConstraintMetadata>());
|
||||
controllerModel.Filters.AddRange(attributes.OfType<IFilter>());
|
||||
controllerModel.RouteConstraints.AddRange(attributes.OfType<RouteConstraintAttribute>());
|
||||
controllerModel.RouteConstraints.AddRange(attributes.OfType<IRouteConstraintProvider>());
|
||||
|
||||
controllerModel.AttributeRoutes.AddRange(
|
||||
attributes.OfType<IRouteTemplateProvider>().Select(rtp => new AttributeRouteModel(rtp)));
|
||||
|
|
@ -142,4 +142,4 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
return controllerModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,11 +56,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
actionDescriptor.ControllerTypeInfo = controller.ControllerType;
|
||||
|
||||
AddApiExplorerInfo(actionDescriptor, action, controller);
|
||||
AddRouteConstraints(actionDescriptor, controller, action);
|
||||
AddControllerRouteConstraints(
|
||||
actionDescriptor,
|
||||
controller.RouteConstraints,
|
||||
removalConstraints);
|
||||
AddRouteConstraints(removalConstraints, actionDescriptor, controller, action);
|
||||
|
||||
if (IsAttributeRoutedAction(actionDescriptor))
|
||||
{
|
||||
|
|
@ -371,39 +367,17 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
|
||||
public static void AddRouteConstraints(
|
||||
ISet<string> removalConstraints,
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
ControllerModel controller,
|
||||
ActionModel action)
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
"controller",
|
||||
controller.ControllerName));
|
||||
|
||||
if (action.IsActionNameMatchRequired)
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
"action",
|
||||
action.ActionName));
|
||||
}
|
||||
else
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
"action",
|
||||
string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddControllerRouteConstraints(
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
IList<RouteConstraintAttribute> routeconstraints,
|
||||
ISet<string> removalConstraints)
|
||||
{
|
||||
// Apply all the constraints defined on the controller (for example, [Area]) to the actions
|
||||
// in that controller. Also keep track of all the constraints that require preventing actions
|
||||
// Apply all the constraints defined on the action, then controller (for example, [Area])
|
||||
// to the actions. Also keep track of all the constraints that require preventing actions
|
||||
// without the constraint to match. For example, actions without an [Area] attribute on their
|
||||
// controller should not match when a value has been given for area when matching a url or
|
||||
// generating a link.
|
||||
foreach (var constraintAttribute in routeconstraints)
|
||||
foreach (var constraintAttribute in action.RouteConstraints)
|
||||
{
|
||||
if (constraintAttribute.BlockNonAttributedActions)
|
||||
{
|
||||
|
|
@ -427,6 +401,55 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var constraintAttribute in controller.RouteConstraints)
|
||||
{
|
||||
if (constraintAttribute.BlockNonAttributedActions)
|
||||
{
|
||||
removalConstraints.Add(constraintAttribute.RouteKey);
|
||||
}
|
||||
|
||||
// Skip duplicates - this also means that a value on the action will take precedence
|
||||
if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey))
|
||||
{
|
||||
if (constraintAttribute.RouteKeyHandling == RouteKeyHandling.CatchAll)
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(
|
||||
RouteDataActionConstraint.CreateCatchAll(
|
||||
constraintAttribute.RouteKey));
|
||||
}
|
||||
else
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
constraintAttribute.RouteKey,
|
||||
constraintAttribute.RouteValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly add the 'default' values
|
||||
if (!HasConstraint(actionDescriptor.RouteConstraints, "action"))
|
||||
{
|
||||
if (action.IsActionNameMatchRequired)
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
"action",
|
||||
action.ActionName));
|
||||
}
|
||||
else
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
"action",
|
||||
string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
if (!HasConstraint(actionDescriptor.RouteConstraints, "controller"))
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
"controller",
|
||||
controller.ControllerName));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasConstraint(List<RouteDataActionConstraint> constraints, string routeKey)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,8 @@ namespace Microsoft.AspNet.Mvc.Logging
|
|||
ActionMethod = inner.ActionMethod;
|
||||
ApiExplorer = new ApiExplorerModelValues(inner.ApiExplorer);
|
||||
Parameters = inner.Parameters.Select(p => new ParameterModelValues(p)).ToList();
|
||||
RouteConstraints = inner.RouteConstraints.Select(
|
||||
r => new RouteConstraintProviderValues(r)).ToList();
|
||||
Filters = inner.Filters.Select(f => new FilterValues(f)).ToList();
|
||||
if (inner.AttributeRouteModel != null)
|
||||
{
|
||||
|
|
@ -60,6 +62,12 @@ namespace Microsoft.AspNet.Mvc.Logging
|
|||
/// See <see cref="ActionModel.Filters"/>.
|
||||
/// </summary>
|
||||
public List<FilterValues> Filters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The route constraints on the controller as <see cref="RouteConstraintProviderValues"/>.
|
||||
/// See <see cref="ControllerModel.RouteConstraints"/>.
|
||||
/// </summary>
|
||||
public List<RouteConstraintProviderValues> RouteConstraints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The attribute route model of the action as <see cref="AttributeRouteModelValues"/>.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Mvc.Logging
|
|||
Filters = inner.Filters.Select(f => new FilterValues(f)).ToList();
|
||||
ActionConstraints = inner.ActionConstraints?.Select(a => new ActionConstraintValues(a))?.ToList();
|
||||
RouteConstraints = inner.RouteConstraints.Select(
|
||||
r => new RouteConstraintAttributeValues(r)).ToList();
|
||||
r => new RouteConstraintProviderValues(r)).ToList();
|
||||
AttributeRoutes = inner.AttributeRoutes.Select(
|
||||
a => new AttributeRouteModelValues(a)).ToList();
|
||||
}
|
||||
|
|
@ -72,10 +72,10 @@ namespace Microsoft.AspNet.Mvc.Logging
|
|||
public List<ActionConstraintValues> ActionConstraints { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The route constraints on the controller as <see cref="RouteConstraintAttributeValues"/>.
|
||||
/// The route constraints on the controller as <see cref="RouteConstraintProviderValues"/>.
|
||||
/// See <see cref="ControllerModel.RouteConstraints"/>.
|
||||
/// </summary>
|
||||
public List<RouteConstraintAttributeValues> RouteConstraints { get; set; }
|
||||
public List<RouteConstraintProviderValues> RouteConstraints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The attribute routes on the controller as <see cref="AttributeRouteModelValues"/>.
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ using Microsoft.Framework.Logging;
|
|||
namespace Microsoft.AspNet.Mvc.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Logging representation of a <see cref="RouteConstraintAttribute"/>. Logged as a substructure of
|
||||
/// Logging representation of a <see cref="IRouteConstraintProvider"/>. Logged as a substructure of
|
||||
/// <see cref="ControllerModelValues"/>
|
||||
/// </summary>
|
||||
public class RouteConstraintAttributeValues : LoggerStructureBase
|
||||
public class RouteConstraintProviderValues : LoggerStructureBase
|
||||
{
|
||||
public RouteConstraintAttributeValues([NotNull] RouteConstraintAttribute inner)
|
||||
public RouteConstraintProviderValues([NotNull] IRouteConstraintProvider inner)
|
||||
{
|
||||
RouteKey = inner.RouteKey;
|
||||
RouteValue = inner.RouteValue;
|
||||
|
|
@ -20,22 +20,22 @@ namespace Microsoft.AspNet.Mvc.Logging
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route value key. See <see cref="RouteConstraintAttribute.RouteKey"/>.
|
||||
/// The route value key. See <see cref="IRouteConstraintProvider.RouteKey"/>.
|
||||
/// </summary>
|
||||
public string RouteKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The expected route value. See <see cref="RouteConstraintAttribute.RouteValue"/>.
|
||||
/// The expected route value. See <see cref="IRouteConstraintProvider.RouteValue"/>.
|
||||
/// </summary>
|
||||
public string RouteValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="RouteKeyHandling"/>. See <see cref="RouteConstraintAttribute.RouteKeyHandling"/>.
|
||||
/// The <see cref="RouteKeyHandling"/>. See <see cref="IRouteConstraintProvider.RouteKeyHandling"/>.
|
||||
/// </summary>
|
||||
public RouteKeyHandling RouteKeyHandling { get; }
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="RouteConstraintAttribute.BlockNonAttributedActions"/>.
|
||||
/// See <see cref="IRouteConstraintProvider.BlockNonAttributedActions"/>.
|
||||
/// </summary>
|
||||
public bool BlockNonAttributedActions { get; }
|
||||
|
||||
|
|
@ -44,4 +44,4 @@ namespace Microsoft.AspNet.Mvc.Logging
|
|||
return LogFormatter.FormatStructure(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,11 +15,9 @@ namespace Microsoft.AspNet.Mvc
|
|||
///
|
||||
/// 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
|
||||
public abstract class RouteConstraintAttribute : Attribute, IRouteConstraintProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RouteConstraintAttribute"/>.
|
||||
|
|
@ -65,25 +63,16 @@ namespace Microsoft.AspNet.Mvc
|
|||
BlockNonAttributedActions = blockNonAttributedActions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The route value key.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public string RouteKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="RouteKeyHandling"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public RouteKeyHandling RouteKeyHandling { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The expected route value. Will be null unless <see cref="RouteConstraintAttribute.RouteKeyHandling"/> is
|
||||
/// set to <see cref="RouteKeyHandling.RequireKey"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public string RouteValue { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to negate this constraint on all actions that do not define a behavior for this route key.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public bool BlockNonAttributedActions { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
action.Filters.Add(new AuthorizeAttribute());
|
||||
action.HttpMethods.Add("GET");
|
||||
action.IsActionNameMatchRequired = true;
|
||||
action.RouteConstraints.Add(new AreaAttribute("Admin"));
|
||||
|
||||
// Act
|
||||
var action2 = new ActionModel(action);
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
|
|||
var expected =
|
||||
"While processing template '[area]/[controller]/[action2]', " +
|
||||
"a replacement value for the token 'action2' could not be found. " +
|
||||
"Available tokens: 'area, controller, action'.";
|
||||
"Available tokens: 'action, area, controller'.";
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
|
|
|
|||
|
|
@ -547,7 +547,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
"For action: 'Microsoft.AspNet.Mvc.Test.ControllerActionDescriptorProviderTests+" +
|
||||
"MultipleErrorsController.Unknown'" + Environment.NewLine +
|
||||
"Error: While processing template 'stub/[action]/[unknown]', a replacement value for the token 'unknown' " +
|
||||
"could not be found. Available tokens: 'controller, action'." + Environment.NewLine +
|
||||
"could not be found. Available tokens: 'action, controller'." + Environment.NewLine +
|
||||
Environment.NewLine +
|
||||
"Error 2:" + Environment.NewLine +
|
||||
"For action: 'Microsoft.AspNet.Mvc.Test.ControllerActionDescriptorProviderTests+" +
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNet.Mvc.Logging
|
||||
{
|
||||
public class RouteConstraintAttributeValuesTest
|
||||
public class RouteConstraintProviderValuesTest
|
||||
{
|
||||
[Fact]
|
||||
public void RouteConstraintAttributeValues_IncludesAllProperties()
|
||||
public void RouteConstraintProviderValues_IncludesAllProperties()
|
||||
{
|
||||
// Arrange
|
||||
var exclude = new[] { "TypeId" };
|
||||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.Logging
|
|||
// Assert
|
||||
PropertiesAssert.PropertiesAreTheSame(
|
||||
typeof(RouteConstraintAttribute),
|
||||
typeof(RouteConstraintAttributeValues),
|
||||
typeof(RouteConstraintProviderValues),
|
||||
exclude);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue