зеркало из https://github.com/aspnet/Mvc.git
[Fixes #731] Add unit tests for ReflectedActionDescriptorProvider
1. Added tests that cover parameters in actions. 2. Added tests that cover building the reflected application model. 3. Added tests that cover attribute routed action constraints and default values. 4. Added tests that cover conventionally routed action constraints and default values. 5. Refactored and cleaned up ReflectedActionDescriptorProvider. All the refactors consist of extracting blocks of code to separate methods to better display the flow when building the action descriptors.
This commit is contained in:
Родитель
8fd7cd51e2
Коммит
9345afeed2
|
@ -112,12 +112,6 @@ namespace Microsoft.AspNet.Mvc
|
|||
return applicationModel;
|
||||
}
|
||||
|
||||
private bool HasConstraint(List<RouteDataActionConstraint> constraints, string routeKey)
|
||||
{
|
||||
return constraints.Any(
|
||||
rc => string.Equals(rc.RouteKey, routeKey, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public List<ReflectedActionDescriptor> Build(ReflectedApplicationModel model)
|
||||
{
|
||||
var actions = new List<ReflectedActionDescriptor>();
|
||||
|
@ -132,162 +126,32 @@ namespace Microsoft.AspNet.Mvc
|
|||
var controllerDescriptor = new ControllerDescriptor(controller.ControllerType);
|
||||
foreach (var action in controller.Actions)
|
||||
{
|
||||
var parameterDescriptors = new List<ParameterDescriptor>();
|
||||
foreach (var parameter in action.Parameters)
|
||||
{
|
||||
var isFromBody = parameter.Attributes.OfType<FromBodyAttribute>().Any();
|
||||
var actionDescriptor = CreateActionDescriptor(
|
||||
action,
|
||||
controller,
|
||||
controllerDescriptor,
|
||||
model.Filters);
|
||||
|
||||
parameterDescriptors.Add(new ParameterDescriptor()
|
||||
{
|
||||
Name = parameter.ParameterName,
|
||||
IsOptional = parameter.IsOptional,
|
||||
AddActionConstraints(actionDescriptor, action, controller);
|
||||
AddControllerRouteConstraints(actionDescriptor, controller.RouteConstraints, removalConstraints);
|
||||
|
||||
ParameterBindingInfo = isFromBody
|
||||
? null
|
||||
: new ParameterBindingInfo(
|
||||
parameter.ParameterName,
|
||||
parameter.ParameterInfo.ParameterType),
|
||||
|
||||
BodyParameterInfo = isFromBody
|
||||
? new BodyParameterInfo(parameter.ParameterInfo.ParameterType)
|
||||
: null
|
||||
});
|
||||
}
|
||||
|
||||
var combinedRoute = ReflectedAttributeRouteModel.CombineReflectedAttributeRouteModel(
|
||||
controller.AttributeRouteModel,
|
||||
action.AttributeRouteModel);
|
||||
|
||||
var attributeRouteInfo = combinedRoute == null ? null : new AttributeRouteInfo()
|
||||
{
|
||||
Template = combinedRoute.Template,
|
||||
Order = combinedRoute.Order ?? DefaultAttributeRouteOrder,
|
||||
Name = combinedRoute.Name,
|
||||
};
|
||||
|
||||
var actionDescriptor = new ReflectedActionDescriptor()
|
||||
{
|
||||
Name = action.ActionName,
|
||||
ControllerDescriptor = controllerDescriptor,
|
||||
MethodInfo = action.ActionMethod,
|
||||
Parameters = parameterDescriptors,
|
||||
RouteConstraints = new List<RouteDataActionConstraint>(),
|
||||
AttributeRouteInfo = attributeRouteInfo
|
||||
};
|
||||
|
||||
actionDescriptor.DisplayName = string.Format(
|
||||
"{0}.{1}",
|
||||
action.ActionMethod.DeclaringType.FullName,
|
||||
action.ActionMethod.Name);
|
||||
|
||||
var httpMethods = action.HttpMethods;
|
||||
if (httpMethods != null && httpMethods.Count > 0)
|
||||
{
|
||||
actionDescriptor.MethodConstraints = new List<HttpMethodConstraint>()
|
||||
{
|
||||
new HttpMethodConstraint(httpMethods)
|
||||
};
|
||||
}
|
||||
|
||||
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",
|
||||
RouteKeyHandling.DenyKey));
|
||||
}
|
||||
|
||||
foreach (var constraintAttribute in controller.RouteConstraints)
|
||||
{
|
||||
if (constraintAttribute.BlockNonAttributedActions)
|
||||
{
|
||||
removalConstraints.Add(constraintAttribute.RouteKey);
|
||||
}
|
||||
|
||||
// Skip duplicates
|
||||
if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey))
|
||||
{
|
||||
if (constraintAttribute.RouteValue == null)
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
constraintAttribute.RouteKey,
|
||||
constraintAttribute.RouteKeyHandling));
|
||||
}
|
||||
else
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
constraintAttribute.RouteKey,
|
||||
constraintAttribute.RouteValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (actionDescriptor.AttributeRouteInfo != null &&
|
||||
actionDescriptor.AttributeRouteInfo.Template != null)
|
||||
if (IsAttributeRoutedAction(actionDescriptor))
|
||||
{
|
||||
hasAttributeRoutes = true;
|
||||
|
||||
// An attribute routed action will ignore conventional routed constraints. We still
|
||||
// want to provide these values as ambient values.
|
||||
foreach (var constraint in actionDescriptor.RouteConstraints)
|
||||
{
|
||||
// We don't need to do anything with attribute routing for 'catch all' behavior. Order
|
||||
// and predecedence of attribute routes allow this kind of behavior.
|
||||
if (constraint.KeyHandling == RouteKeyHandling.RequireKey ||
|
||||
constraint.KeyHandling == RouteKeyHandling.DenyKey)
|
||||
{
|
||||
actionDescriptor.RouteValueDefaults.Add(constraint.RouteKey, constraint.RouteValue);
|
||||
}
|
||||
}
|
||||
// want to provide these values as ambient values for link generation.
|
||||
AddConstraintsAsDefaultRouteValues(actionDescriptor);
|
||||
|
||||
// Replaces tokens like [controller]/[action] in the route template with the actual values
|
||||
// for this action.
|
||||
var templateText = actionDescriptor.AttributeRouteInfo.Template;
|
||||
try
|
||||
{
|
||||
templateText = ReflectedAttributeRouteModel.ReplaceTokens(
|
||||
templateText,
|
||||
actionDescriptor.RouteValueDefaults);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
var message = Resources.FormatAttributeRoute_IndividualErrorMessage(
|
||||
actionDescriptor.DisplayName,
|
||||
Environment.NewLine,
|
||||
ex.Message);
|
||||
ReplaceAttributeRouteTokens(actionDescriptor, routeTemplateErrors);
|
||||
|
||||
routeTemplateErrors.Add(message);
|
||||
}
|
||||
|
||||
actionDescriptor.AttributeRouteInfo.Template = templateText;
|
||||
|
||||
var routeGroupValue = GetRouteGroupValue(
|
||||
actionDescriptor.AttributeRouteInfo.Order,
|
||||
templateText);
|
||||
|
||||
var routeConstraints = new List<RouteDataActionConstraint>();
|
||||
routeConstraints.Add(new RouteDataActionConstraint(
|
||||
AttributeRouting.RouteGroupKey,
|
||||
routeGroupValue));
|
||||
|
||||
actionDescriptor.RouteConstraints = routeConstraints;
|
||||
// Attribute routed actions will ignore conventional routed constraints. Instead they have
|
||||
// a single route constraint "RouteGroup" associated with it.
|
||||
ReplaceRouteConstraints(actionDescriptor);
|
||||
}
|
||||
|
||||
actionDescriptor.FilterDescriptors =
|
||||
action.Filters.Select(f => new FilterDescriptor(f, FilterScope.Action))
|
||||
.Concat(controller.Filters.Select(f => new FilterDescriptor(f, FilterScope.Controller)))
|
||||
.Concat(model.Filters.Select(f => new FilterDescriptor(f, FilterScope.Global)))
|
||||
.OrderBy(d => d, FilterDescriptorOrderComparer.Comparer)
|
||||
.ToList();
|
||||
|
||||
actions.Add(actionDescriptor);
|
||||
}
|
||||
}
|
||||
|
@ -297,11 +161,10 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
foreach (var actionDescriptor in actions)
|
||||
{
|
||||
if (actionDescriptor.AttributeRouteInfo == null ||
|
||||
actionDescriptor.AttributeRouteInfo.Template == null)
|
||||
if (!IsAttributeRoutedAction(actionDescriptor))
|
||||
{
|
||||
// Any any attribute routes are in use, then non-attribute-routed ADs can't be selected
|
||||
// when a route group returned by the route.
|
||||
// Any attribute routes are in use, then non-attribute-routed action descriptors can't be
|
||||
// selected when a route group returned by the route.
|
||||
if (hasAttributeRoutes)
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
|
@ -309,15 +172,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
RouteKeyHandling.DenyKey));
|
||||
}
|
||||
|
||||
foreach (var key in removalConstraints)
|
||||
{
|
||||
if (!HasConstraint(actionDescriptor.RouteConstraints, key))
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
key,
|
||||
RouteKeyHandling.DenyKey));
|
||||
}
|
||||
}
|
||||
// Add a route constraint with DenyKey for each constraint in the set to all the
|
||||
// actions that don't have that constraint. For example, if a controller defines
|
||||
// an area constraint, all actions that don't belong to an area must have a route
|
||||
// constraint that prevents them from matching an incomming request.
|
||||
AddRemovalConstraints(actionDescriptor, removalConstraints);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -326,18 +185,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
// Build a map of attribute route name to action descriptors to ensure that all
|
||||
// attribute routes with a given name have the same template.
|
||||
IList<ActionDescriptor> namedActionGroup;
|
||||
|
||||
if (actionsByRouteName.TryGetValue(attributeRouteInfo.Name, out namedActionGroup))
|
||||
{
|
||||
namedActionGroup.Add(actionDescriptor);
|
||||
}
|
||||
else
|
||||
{
|
||||
namedActionGroup = new List<ActionDescriptor>();
|
||||
namedActionGroup.Add(actionDescriptor);
|
||||
actionsByRouteName.Add(attributeRouteInfo.Name, namedActionGroup);
|
||||
}
|
||||
AddActionToNamedGroup(actionsByRouteName, attributeRouteInfo.Name, actionDescriptor);
|
||||
}
|
||||
|
||||
// We still want to add a 'null' for any constraint with DenyKey so that link generation
|
||||
|
@ -379,7 +227,251 @@ namespace Microsoft.AspNet.Mvc
|
|||
return actions;
|
||||
}
|
||||
|
||||
private static IList<string> AddErrorNumbers(IList<string> namedRoutedErrors)
|
||||
private static ReflectedActionDescriptor CreateActionDescriptor(ReflectedActionModel action,
|
||||
ReflectedControllerModel controller,
|
||||
ControllerDescriptor controllerDescriptor,
|
||||
IEnumerable<IFilter> globalFilters)
|
||||
{
|
||||
var parameterDescriptors = new List<ParameterDescriptor>();
|
||||
foreach (var parameter in action.Parameters)
|
||||
{
|
||||
var isFromBody = parameter.Attributes.OfType<FromBodyAttribute>().Any();
|
||||
var paramDescriptor = new ParameterDescriptor()
|
||||
{
|
||||
Name = parameter.ParameterName,
|
||||
IsOptional = parameter.IsOptional
|
||||
};
|
||||
|
||||
if (isFromBody)
|
||||
{
|
||||
paramDescriptor.BodyParameterInfo = new BodyParameterInfo(
|
||||
parameter.ParameterInfo.ParameterType);
|
||||
}
|
||||
else
|
||||
{
|
||||
paramDescriptor.ParameterBindingInfo = new ParameterBindingInfo(
|
||||
parameter.ParameterName,
|
||||
parameter.ParameterInfo.ParameterType);
|
||||
}
|
||||
|
||||
parameterDescriptors.Add(paramDescriptor);
|
||||
}
|
||||
|
||||
var attributeRouteInfo = CreateAttributeRouteInfo(action, controller);
|
||||
|
||||
var actionDescriptor = new ReflectedActionDescriptor()
|
||||
{
|
||||
Name = action.ActionName,
|
||||
ControllerDescriptor = controllerDescriptor,
|
||||
MethodInfo = action.ActionMethod,
|
||||
Parameters = parameterDescriptors,
|
||||
RouteConstraints = new List<RouteDataActionConstraint>(),
|
||||
AttributeRouteInfo = attributeRouteInfo
|
||||
};
|
||||
|
||||
actionDescriptor.DisplayName = string.Format(
|
||||
"{0}.{1}",
|
||||
action.ActionMethod.DeclaringType.FullName,
|
||||
action.ActionMethod.Name);
|
||||
|
||||
actionDescriptor.FilterDescriptors =
|
||||
action.Filters.Select(f => new FilterDescriptor(f, FilterScope.Action))
|
||||
.Concat(controller.Filters.Select(f => new FilterDescriptor(f, FilterScope.Controller)))
|
||||
.Concat(globalFilters.Select(f => new FilterDescriptor(f, FilterScope.Global)))
|
||||
.OrderBy(d => d, FilterDescriptorOrderComparer.Comparer)
|
||||
.ToList();
|
||||
|
||||
return actionDescriptor;
|
||||
}
|
||||
|
||||
private static AttributeRouteInfo CreateAttributeRouteInfo(
|
||||
ReflectedActionModel action,
|
||||
ReflectedControllerModel controller)
|
||||
{
|
||||
var combinedRoute = ReflectedAttributeRouteModel.CombineReflectedAttributeRouteModel(
|
||||
controller.AttributeRouteModel,
|
||||
action.AttributeRouteModel);
|
||||
|
||||
if (combinedRoute == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new AttributeRouteInfo()
|
||||
{
|
||||
Template = combinedRoute.Template,
|
||||
Order = combinedRoute.Order ?? DefaultAttributeRouteOrder,
|
||||
Name = combinedRoute.Name,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddActionConstraints(
|
||||
ReflectedActionDescriptor actionDescriptor,
|
||||
ReflectedActionModel action,
|
||||
ReflectedControllerModel controller)
|
||||
{
|
||||
var httpMethods = action.HttpMethods;
|
||||
if (httpMethods != null && httpMethods.Count > 0)
|
||||
{
|
||||
actionDescriptor.MethodConstraints = new List<HttpMethodConstraint>()
|
||||
{
|
||||
new HttpMethodConstraint(httpMethods)
|
||||
};
|
||||
}
|
||||
|
||||
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",
|
||||
RouteKeyHandling.DenyKey));
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddControllerRouteConstraints(
|
||||
ReflectedActionDescriptor 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
|
||||
// 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)
|
||||
{
|
||||
if (constraintAttribute.BlockNonAttributedActions)
|
||||
{
|
||||
removalConstraints.Add(constraintAttribute.RouteKey);
|
||||
}
|
||||
|
||||
// Skip duplicates
|
||||
if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey))
|
||||
{
|
||||
if (constraintAttribute.RouteValue == null)
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
constraintAttribute.RouteKey,
|
||||
constraintAttribute.RouteKeyHandling));
|
||||
}
|
||||
else
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
constraintAttribute.RouteKey,
|
||||
constraintAttribute.RouteValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasConstraint(List<RouteDataActionConstraint> constraints, string routeKey)
|
||||
{
|
||||
return constraints.Any(
|
||||
rc => string.Equals(rc.RouteKey, routeKey, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static void ReplaceRouteConstraints(ReflectedActionDescriptor actionDescriptor)
|
||||
{
|
||||
var routeGroupValue = GetRouteGroupValue(
|
||||
actionDescriptor.AttributeRouteInfo.Order,
|
||||
actionDescriptor.AttributeRouteInfo.Template);
|
||||
|
||||
var routeConstraints = new List<RouteDataActionConstraint>();
|
||||
routeConstraints.Add(new RouteDataActionConstraint(
|
||||
AttributeRouting.RouteGroupKey,
|
||||
routeGroupValue));
|
||||
|
||||
actionDescriptor.RouteConstraints = routeConstraints;
|
||||
}
|
||||
|
||||
private static void ReplaceAttributeRouteTokens(
|
||||
ReflectedActionDescriptor actionDescriptor,
|
||||
IList<string> routeTemplateErrors)
|
||||
{
|
||||
try
|
||||
{
|
||||
actionDescriptor.AttributeRouteInfo.Template = ReflectedAttributeRouteModel.ReplaceTokens(
|
||||
actionDescriptor.AttributeRouteInfo.Template,
|
||||
actionDescriptor.RouteValueDefaults);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
var message = Resources.FormatAttributeRoute_IndividualErrorMessage(
|
||||
actionDescriptor.DisplayName,
|
||||
Environment.NewLine,
|
||||
ex.Message);
|
||||
|
||||
routeTemplateErrors.Add(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddConstraintsAsDefaultRouteValues(ReflectedActionDescriptor actionDescriptor)
|
||||
{
|
||||
foreach (var constraint in actionDescriptor.RouteConstraints)
|
||||
{
|
||||
// We don't need to do anything with attribute routing for 'catch all' behavior. Order
|
||||
// and predecedence 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(
|
||||
ReflectedActionDescriptor actionDescriptor,
|
||||
ISet<string> removalConstraints)
|
||||
{
|
||||
foreach (var key in removalConstraints)
|
||||
{
|
||||
if (!HasConstraint(actionDescriptor.RouteConstraints, key))
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
key,
|
||||
RouteKeyHandling.DenyKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddActionToNamedGroup(
|
||||
IDictionary<string, IList<ActionDescriptor>> actionsByRouteName,
|
||||
string routeName,
|
||||
ReflectedActionDescriptor actionDescriptor)
|
||||
{
|
||||
IList<ActionDescriptor> namedActionGroup;
|
||||
|
||||
if (actionsByRouteName.TryGetValue(routeName, out namedActionGroup))
|
||||
{
|
||||
namedActionGroup.Add(actionDescriptor);
|
||||
}
|
||||
else
|
||||
{
|
||||
namedActionGroup = new List<ActionDescriptor>();
|
||||
namedActionGroup.Add(actionDescriptor);
|
||||
actionsByRouteName.Add(routeName, namedActionGroup);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAttributeRoutedAction(ReflectedActionDescriptor actionDescriptor)
|
||||
{
|
||||
return actionDescriptor.AttributeRouteInfo != null &&
|
||||
actionDescriptor.AttributeRouteInfo.Template != null;
|
||||
}
|
||||
|
||||
private static IList<string> AddErrorNumbers(
|
||||
IList<string> namedRoutedErrors)
|
||||
{
|
||||
return namedRoutedErrors
|
||||
.Select((nre, i) =>
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
var actionNames = descriptors.Select(ad => ad.Name);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new[] { "GetPerson", "ListPeople", }, actionNames);
|
||||
Assert.Equal(new[] { "GetPerson", "ShowPeople", }, actionNames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -58,21 +58,244 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
Assert.Equal(FilterScope.Action, filter3.Scope);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_AddsHttpMethodConstraints()
|
||||
[Theory]
|
||||
[InlineData(typeof(HttpMethodController), nameof(HttpMethodController.OnlyPost), "POST")]
|
||||
[InlineData(typeof(AttributeRoutedHttpMethodController), nameof(AttributeRoutedHttpMethodController.PutOrPatch), "PUT,PATCH")]
|
||||
public void GetDescriptors_AddsHttpMethodConstraints(Type controllerType, string actionName, string expectedMethods)
|
||||
{
|
||||
// Arrange
|
||||
var provider = GetProvider(typeof(HttpMethodController).GetTypeInfo());
|
||||
var provider = GetProvider(controllerType.GetTypeInfo());
|
||||
|
||||
// Act
|
||||
var descriptors = provider.GetDescriptors();
|
||||
var descriptor = Assert.Single(descriptors);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("OnlyPost", descriptor.Name);
|
||||
Assert.Equal(actionName, descriptor.Name);
|
||||
|
||||
Assert.Single(descriptor.MethodConstraints);
|
||||
Assert.Equal(new string[] { "POST" }, descriptor.MethodConstraints[0].HttpMethods);
|
||||
Assert.Equal(expectedMethods.Split(','), descriptor.MethodConstraints[0].HttpMethods);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_AddsParameters_ToActionDescriptor()
|
||||
{
|
||||
// Arrange & Act
|
||||
var descriptors = GetDescriptors(
|
||||
typeof(ActionParametersController).GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
var main = Assert.Single(descriptors,
|
||||
d => d.Name.Equals(nameof(ActionParametersController.RequiredInt)));
|
||||
|
||||
Assert.NotNull(main.Parameters);
|
||||
var id = Assert.Single(main.Parameters);
|
||||
|
||||
Assert.Equal("id", id.Name);
|
||||
Assert.False(id.IsOptional);
|
||||
Assert.Null(id.BodyParameterInfo);
|
||||
|
||||
Assert.NotNull(id.ParameterBindingInfo);
|
||||
Assert.Equal("id", id.ParameterBindingInfo.Prefix);
|
||||
Assert.Equal(typeof(int), id.ParameterBindingInfo.ParameterType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_AddsMultipleParameters_ToActionDescriptor()
|
||||
{
|
||||
// Arrange & Act
|
||||
var descriptors = GetDescriptors(
|
||||
typeof(ActionParametersController).GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
var main = Assert.Single(descriptors,
|
||||
d => d.Name.Equals(nameof(ActionParametersController.MultipleParameters)));
|
||||
|
||||
Assert.NotNull(main.Parameters);
|
||||
var id = Assert.Single(main.Parameters, p => p.Name == "id");
|
||||
|
||||
Assert.Equal("id", id.Name);
|
||||
Assert.False(id.IsOptional);
|
||||
Assert.Null(id.BodyParameterInfo);
|
||||
|
||||
Assert.NotNull(id.ParameterBindingInfo);
|
||||
Assert.Equal("id", id.ParameterBindingInfo.Prefix);
|
||||
Assert.Equal(typeof(int), id.ParameterBindingInfo.ParameterType);
|
||||
|
||||
var entity = Assert.Single(main.Parameters, p => p.Name == "entity");
|
||||
|
||||
Assert.Equal("entity", entity.Name);
|
||||
Assert.False(entity.IsOptional);
|
||||
Assert.Null(entity.ParameterBindingInfo);
|
||||
|
||||
Assert.NotNull(entity.BodyParameterInfo);
|
||||
Assert.Equal(typeof(TestActionParameter), entity.BodyParameterInfo.ParameterType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_AddsMultipleParametersWithDifferentCasing_ToActionDescriptor()
|
||||
{
|
||||
// Arrange & Act
|
||||
var descriptors = GetDescriptors(
|
||||
typeof(ActionParametersController).GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
var main = Assert.Single(descriptors,
|
||||
d => d.Name.Equals(nameof(ActionParametersController.DifferentCasing)));
|
||||
|
||||
Assert.NotNull(main.Parameters);
|
||||
var id = Assert.Single(main.Parameters, p => p.Name == "id");
|
||||
|
||||
Assert.Equal("id", id.Name);
|
||||
Assert.False(id.IsOptional);
|
||||
Assert.Null(id.BodyParameterInfo);
|
||||
|
||||
Assert.NotNull(id.ParameterBindingInfo);
|
||||
Assert.Equal("id", id.ParameterBindingInfo.Prefix);
|
||||
Assert.Equal(typeof(int), id.ParameterBindingInfo.ParameterType);
|
||||
|
||||
var upperCaseId = Assert.Single(main.Parameters, p => p.Name == "ID");
|
||||
|
||||
Assert.Equal("ID", upperCaseId.Name);
|
||||
Assert.False(upperCaseId.IsOptional);
|
||||
Assert.Null(upperCaseId.BodyParameterInfo);
|
||||
|
||||
Assert.NotNull(upperCaseId.ParameterBindingInfo);
|
||||
Assert.Equal("ID", upperCaseId.ParameterBindingInfo.Prefix);
|
||||
Assert.Equal(typeof(int), upperCaseId.ParameterBindingInfo.ParameterType);
|
||||
|
||||
var pascalCaseId = Assert.Single(main.Parameters, p => p.Name == "Id");
|
||||
|
||||
Assert.Equal("Id", pascalCaseId.Name);
|
||||
Assert.False(pascalCaseId.IsOptional);
|
||||
Assert.Null(pascalCaseId.BodyParameterInfo);
|
||||
|
||||
Assert.NotNull(pascalCaseId.ParameterBindingInfo);
|
||||
Assert.Equal("Id", pascalCaseId.ParameterBindingInfo.Prefix);
|
||||
Assert.Equal(typeof(int), pascalCaseId.ParameterBindingInfo.ParameterType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(nameof(ActionParametersController.OptionalInt), typeof(Nullable<int>))]
|
||||
[InlineData(nameof(ActionParametersController.OptionalChar), typeof(char))]
|
||||
public void GetDescriptors_AddsParametersWithDefaultValues_AsOptionalParameters(
|
||||
string actionName,
|
||||
Type parameterType)
|
||||
{
|
||||
// Arrange & Act
|
||||
var descriptors = GetDescriptors(
|
||||
typeof(ActionParametersController).GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
var optional = Assert.Single(descriptors,
|
||||
d => d.Name.Equals(actionName));
|
||||
|
||||
Assert.NotNull(optional.Parameters);
|
||||
var id = Assert.Single(optional.Parameters);
|
||||
|
||||
Assert.Equal("id", id.Name);
|
||||
Assert.True(id.IsOptional);
|
||||
Assert.Null(id.BodyParameterInfo);
|
||||
|
||||
Assert.NotNull(id.ParameterBindingInfo);
|
||||
Assert.Equal("id", id.ParameterBindingInfo.Prefix);
|
||||
Assert.Equal(parameterType, id.ParameterBindingInfo.ParameterType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_AddsParameters_DetectsFromBodyParameters()
|
||||
{
|
||||
// Arrange & Act
|
||||
var actionName = nameof(ActionParametersController.FromBodyParameter);
|
||||
|
||||
var descriptors = GetDescriptors(
|
||||
typeof(ActionParametersController).GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
var fromBody = Assert.Single(descriptors,
|
||||
d => d.Name.Equals(actionName));
|
||||
|
||||
Assert.NotNull(fromBody.Parameters);
|
||||
var entity = Assert.Single(fromBody.Parameters);
|
||||
|
||||
Assert.Equal("entity", entity.Name);
|
||||
Assert.False(entity.IsOptional);
|
||||
|
||||
Assert.NotNull(entity.BodyParameterInfo);
|
||||
Assert.Equal(typeof(TestActionParameter), entity.BodyParameterInfo.ParameterType);
|
||||
|
||||
Assert.Null(entity.ParameterBindingInfo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_AddsParameters_DoesNotDetectParameterFromBody_IfNoFromBodyAttribute()
|
||||
{
|
||||
// Arrange & Act
|
||||
var actionName = nameof(ActionParametersController.NotFromBodyParameter);
|
||||
|
||||
var descriptors = GetDescriptors(
|
||||
typeof(ActionParametersController).GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
var notFromBody = Assert.Single(descriptors,
|
||||
d => d.Name.Equals(actionName));
|
||||
|
||||
Assert.NotNull(notFromBody.Parameters);
|
||||
var entity = Assert.Single(notFromBody.Parameters);
|
||||
|
||||
Assert.Equal("entity", entity.Name);
|
||||
Assert.False(entity.IsOptional);
|
||||
Assert.Null(entity.BodyParameterInfo);
|
||||
|
||||
Assert.NotNull(entity.ParameterBindingInfo);
|
||||
Assert.Equal("entity", entity.ParameterBindingInfo.Prefix);
|
||||
Assert.Equal(typeof(TestActionParameter), entity.ParameterBindingInfo.ParameterType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_AddsControllerAndActionConstraints_ToConventionallyRoutedActions()
|
||||
{
|
||||
// Arrange & Act
|
||||
var descriptors = GetDescriptors(
|
||||
typeof(ConventionallyRoutedController).GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(descriptors);
|
||||
|
||||
Assert.NotNull(action.RouteConstraints);
|
||||
|
||||
var controller = Assert.Single(action.RouteConstraints,
|
||||
rc => rc.RouteKey.Equals("controller"));
|
||||
Assert.Equal(RouteKeyHandling.RequireKey, controller.KeyHandling);
|
||||
Assert.Equal("ConventionallyRouted", controller.RouteValue);
|
||||
|
||||
var actionConstraint = Assert.Single(action.RouteConstraints,
|
||||
rc => rc.RouteKey.Equals("action"));
|
||||
Assert.Equal(RouteKeyHandling.RequireKey, actionConstraint.KeyHandling);
|
||||
Assert.Equal(nameof(ConventionallyRoutedController.ConventionalAction), actionConstraint.RouteValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_AddsControllerAndActionDefaults_ToAttributeRoutedActions()
|
||||
{
|
||||
// Arrange & Act
|
||||
var descriptors = GetDescriptors(
|
||||
typeof(AttributeRoutedController).GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(descriptors);
|
||||
|
||||
var routeconstraint = Assert.Single(action.RouteConstraints);
|
||||
Assert.Equal(RouteKeyHandling.RequireKey, routeconstraint.KeyHandling);
|
||||
Assert.Equal(AttributeRouting.RouteGroupKey, routeconstraint.RouteKey);
|
||||
|
||||
var controller = Assert.Single(action.RouteValueDefaults,
|
||||
rc => rc.Key.Equals("controller"));
|
||||
Assert.Equal("AttributeRouted", controller.Value);
|
||||
|
||||
var actionConstraint = Assert.Single(action.RouteValueDefaults,
|
||||
rc => rc.Key.Equals("action"));
|
||||
Assert.Equal(nameof(AttributeRoutedController.AttributeRoutedAction), actionConstraint.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -110,6 +333,12 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
c =>
|
||||
c.RouteKey == "action" &&
|
||||
c.RouteValue == "Edit");
|
||||
Assert.Single(
|
||||
descriptorWithConstraint.RouteConstraints,
|
||||
c =>
|
||||
c.RouteKey == "key" &&
|
||||
c.RouteValue == "value" &&
|
||||
c.KeyHandling == RouteKeyHandling.RequireKey);
|
||||
|
||||
Assert.Equal(3, descriptorWithoutConstraint.RouteConstraints.Count);
|
||||
Assert.Single(
|
||||
|
@ -122,6 +351,12 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
c =>
|
||||
c.RouteKey == "action" &&
|
||||
c.RouteValue == "OnlyPost");
|
||||
Assert.Single(
|
||||
descriptorWithoutConstraint.RouteConstraints,
|
||||
c =>
|
||||
c.RouteKey == "key" &&
|
||||
c.RouteValue == null &&
|
||||
c.KeyHandling == RouteKeyHandling.DenyKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -158,6 +393,12 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
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(
|
||||
|
@ -190,6 +431,66 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
Assert.Same(filter, Assert.Single(filters));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildModel_CreatesReflectedControllerModels_ForAllControllers()
|
||||
{
|
||||
// Arrange
|
||||
var provider = GetProvider(
|
||||
typeof(ConventionallyRoutedController).GetTypeInfo(),
|
||||
typeof(AttributeRoutedController).GetTypeInfo(),
|
||||
typeof(EmptyController).GetTypeInfo(),
|
||||
typeof(NonActionAttributeController).GetTypeInfo());
|
||||
|
||||
// Act
|
||||
var model = provider.BuildModel();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(model);
|
||||
Assert.Equal(4, model.Controllers.Count);
|
||||
|
||||
var conventional = Assert.Single(model.Controllers,
|
||||
c => c.ControllerName == "ConventionallyRouted");
|
||||
Assert.Null(conventional.AttributeRouteModel);
|
||||
Assert.Single(conventional.Actions);
|
||||
|
||||
var attributeRouted = Assert.Single(model.Controllers,
|
||||
c => c.ControllerName == "AttributeRouted");
|
||||
Assert.Single(attributeRouted.Actions);
|
||||
Assert.NotNull(attributeRouted.AttributeRouteModel);
|
||||
|
||||
var empty = Assert.Single(model.Controllers,
|
||||
c => c.ControllerName == "Empty");
|
||||
Assert.Empty(empty.Actions);
|
||||
|
||||
var nonAction = Assert.Single(model.Controllers,
|
||||
c => c.ControllerName == "NonActionAttribute");
|
||||
Assert.Empty(nonAction.Actions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildModel_CreatesReflectedActionDescriptors_ForValidActions()
|
||||
{
|
||||
// Arrange
|
||||
var provider = GetProvider(
|
||||
typeof(PersonController).GetTypeInfo());
|
||||
|
||||
// Act
|
||||
var model = provider.BuildModel();
|
||||
|
||||
// Assert
|
||||
var controller = Assert.Single(model.Controllers);
|
||||
|
||||
Assert.Equal(2, controller.Actions.Count);
|
||||
|
||||
var getPerson = Assert.Single(controller.Actions, a => a.ActionName == "GetPerson");
|
||||
Assert.Empty(getPerson.HttpMethods);
|
||||
Assert.True(getPerson.IsActionNameMatchRequired);
|
||||
|
||||
var showPeople = Assert.Single(controller.Actions, a => a.ActionName == "ShowPeople");
|
||||
Assert.Empty(showPeople.HttpMethods);
|
||||
Assert.True(showPeople.IsActionNameMatchRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptor_SetsDisplayName()
|
||||
{
|
||||
|
@ -340,7 +641,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
{
|
||||
// Arrange
|
||||
var provider = GetProvider(
|
||||
typeof(MixedAttributeRouteController).GetTypeInfo(),
|
||||
typeof(ConventionalAndAttributeRoutedActionsWithAreaController).GetTypeInfo(),
|
||||
typeof(ConstrainedController).GetTypeInfo());
|
||||
|
||||
// Act
|
||||
|
@ -364,7 +665,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
{
|
||||
// Arrange
|
||||
var provider = GetProvider(
|
||||
typeof(MixedAttributeRouteController).GetTypeInfo(),
|
||||
typeof(ConventionalAndAttributeRoutedActionsWithAreaController).GetTypeInfo(),
|
||||
typeof(ConstrainedController).GetTypeInfo());
|
||||
|
||||
// Act
|
||||
|
@ -385,7 +686,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
Assert.Equal(5, indexAction.RouteValueDefaults.Count);
|
||||
|
||||
var controllerDefault = Assert.Single(indexAction.RouteValueDefaults, rd => rd.Key.Equals("controller", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Equal("MixedAttributeRoute", controllerDefault.Value);
|
||||
Assert.Equal("ConventionalAndAttributeRoutedActionsWithArea", controllerDefault.Value);
|
||||
|
||||
var actionDefault = Assert.Single(indexAction.RouteValueDefaults, rd => rd.Key.Equals("action", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Equal("Index", actionDefault.Value);
|
||||
|
@ -520,11 +821,19 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
}
|
||||
}
|
||||
|
||||
[Route("Products")]
|
||||
private class AttributeRoutedHttpMethodController
|
||||
{
|
||||
[AcceptVerbs("PUT", "PATCH")]
|
||||
public void PutOrPatch() { }
|
||||
}
|
||||
|
||||
private class PersonController
|
||||
{
|
||||
public void GetPerson()
|
||||
{ }
|
||||
|
||||
[ActionName("ShowPeople")]
|
||||
public void ListPeople()
|
||||
{ }
|
||||
|
||||
|
@ -533,7 +842,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
{ }
|
||||
}
|
||||
|
||||
public class MyRouteConstraintAttribute : RouteConstraintAttribute
|
||||
private class MyRouteConstraintAttribute : RouteConstraintAttribute
|
||||
{
|
||||
public MyRouteConstraintAttribute(bool blockNonAttributedActions)
|
||||
: base("key", "value", blockNonAttributedActions)
|
||||
|
@ -541,7 +850,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
}
|
||||
}
|
||||
|
||||
public class MySecondRouteConstraintAttribute : RouteConstraintAttribute
|
||||
private class MySecondRouteConstraintAttribute : RouteConstraintAttribute
|
||||
{
|
||||
public MySecondRouteConstraintAttribute(bool blockNonAttributedActions)
|
||||
: base("second", "value", blockNonAttributedActions)
|
||||
|
@ -549,7 +858,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
}
|
||||
}
|
||||
|
||||
[MyRouteConstraintAttribute(blockNonAttributedActions: true)]
|
||||
[MyRouteConstraint(blockNonAttributedActions: true)]
|
||||
private class BlockNonAttributedActionsController
|
||||
{
|
||||
public void Edit()
|
||||
|
@ -557,7 +866,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
}
|
||||
}
|
||||
|
||||
[MyRouteConstraintAttribute(blockNonAttributedActions: false)]
|
||||
[MyRouteConstraint(blockNonAttributedActions: false)]
|
||||
private class DontBlockNonAttributedActionsController
|
||||
{
|
||||
public void Create()
|
||||
|
@ -623,7 +932,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
}
|
||||
|
||||
[Area("Home")]
|
||||
private class MixedAttributeRouteController
|
||||
private class ConventionalAndAttributeRoutedActionsWithAreaController
|
||||
{
|
||||
[HttpGet("Index")]
|
||||
public void Index() { }
|
||||
|
@ -635,7 +944,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
}
|
||||
|
||||
[Route("Products", Name = "Products")]
|
||||
public class SameNameDifferentTemplatesController
|
||||
private class SameNameDifferentTemplatesController
|
||||
{
|
||||
[HttpGet]
|
||||
public void Get() { }
|
||||
|
@ -668,7 +977,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
public void PatchItems() { }
|
||||
}
|
||||
|
||||
public class DifferentCasingsAttributeRouteNamesController
|
||||
private class DifferentCasingsAttributeRouteNamesController
|
||||
{
|
||||
[HttpGet("{id}", Name = "Products")]
|
||||
public void Get() { }
|
||||
|
@ -689,5 +998,50 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
{
|
||||
public void ConstrainedNonAttributedAction() { }
|
||||
}
|
||||
|
||||
private class ActionParametersController
|
||||
{
|
||||
public void RequiredInt(int id) { }
|
||||
|
||||
public void OptionalInt(int? id = 5) { }
|
||||
|
||||
public void OptionalChar(char id = 'c') { }
|
||||
|
||||
public void FromBodyParameter([FromBody] TestActionParameter entity) { }
|
||||
|
||||
public void NotFromBodyParameter(TestActionParameter entity) { }
|
||||
|
||||
public void MultipleParameters(int id, [FromBody] TestActionParameter entity) { }
|
||||
|
||||
public void DifferentCasing(int id, int ID, int Id) { }
|
||||
}
|
||||
|
||||
private class ConventionallyRoutedController
|
||||
{
|
||||
public void ConventionalAction() { }
|
||||
}
|
||||
|
||||
[Route("api")]
|
||||
private class AttributeRoutedController()
|
||||
{
|
||||
[HttpGet("AttributeRoute")]
|
||||
public void AttributeRoutedAction() { }
|
||||
}
|
||||
|
||||
private class EmptyController
|
||||
{
|
||||
}
|
||||
|
||||
private class NonActionAttributeController
|
||||
{
|
||||
[NonAction]
|
||||
public void Action() { }
|
||||
}
|
||||
|
||||
private class TestActionParameter
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int Name { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче