[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:
jacalvar 2014-08-30 23:33:10 -07:00
Родитель 8fd7cd51e2
Коммит 9345afeed2
2 изменённых файлов: 639 добавлений и 193 удалений

Просмотреть файл

@ -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; }
}
}
}