[Fixes #4043] Figure out ControllerModel.AttributeRoutes

This commit is contained in:
Kiran Challa 2016-02-28 12:41:05 -08:00
parent 5423dc8c34
commit b7cde3e58f
13 changed files with 662 additions and 611 deletions

View File

@ -6,15 +6,12 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
[DebuggerDisplay("Name={ActionName}({Methods()}), Type={Controller.ControllerType.Name}," +
" Route: {AttributeRouteModel?.Template}, Filters: {Filters.Count}")]
[DebuggerDisplay("{Controller.ControllerType.Name}.{ActionMethod.Name}")]
public class ActionModel : ICommonModel, IFilterModel, IApiExplorerModel
{
public ActionModel(
@ -35,12 +32,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
ApiExplorer = new ApiExplorerModel();
Attributes = new List<object>(attributes);
ActionConstraints = new List<IActionConstraintMetadata>();
Filters = new List<IFilterMetadata>();
HttpMethods = new List<string>();
Parameters = new List<ParameterModel>();
RouteConstraints = new List<IRouteConstraintProvider>();
Properties = new Dictionary<object, object>();
Selectors = new List<SelectorModel>();
}
public ActionModel(ActionModel other)
@ -57,25 +53,17 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
Controller = other.Controller;
// These are just metadata, safe to create new collections
ActionConstraints = new List<IActionConstraintMetadata>(other.ActionConstraints);
Attributes = new List<object>(other.Attributes);
Filters = new List<IFilterMetadata>(other.Filters);
HttpMethods = new List<string>(other.HttpMethods);
Properties = new Dictionary<object, object>(other.Properties);
// 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)
{
AttributeRouteModel = new AttributeRouteModel(other.AttributeRouteModel);
}
Selectors = new List<SelectorModel>(other.Selectors.Select(s => new SelectorModel(s)));
}
public IList<IActionConstraintMetadata> ActionConstraints { get; private set; }
public MethodInfo ActionMethod { get; }
public string ActionName { get; set; }
@ -86,25 +74,21 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// <remarks>
/// <see cref="ActionModel.ApiExplorer"/> allows configuration of settings for ApiExplorer
/// which apply to the action.
///
///
/// Settings applied by <see cref="ActionModel.ApiExplorer"/> override settings from
/// <see cref="ApplicationModel.ApiExplorer"/> and <see cref="ControllerModel.ApiExplorer"/>.
/// </remarks>
public ApiExplorerModel ApiExplorer { get; set; }
public AttributeRouteModel AttributeRouteModel { get; set; }
public IReadOnlyList<object> Attributes { get; }
public ControllerModel Controller { get; set; }
public IList<IFilterMetadata> Filters { get; private set; }
public IList<IFilterMetadata> Filters { get; }
public IList<string> HttpMethods { get; private set; }
public IList<ParameterModel> Parameters { get; }
public IList<ParameterModel> Parameters { get; private set; }
public IList<IRouteConstraintProvider> RouteConstraints { get; private set; }
public IList<IRouteConstraintProvider> RouteConstraints { get; }
/// <summary>
/// Gets a set of properties associated with the action.
@ -120,14 +104,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
string ICommonModel.Name => ActionName;
private string Methods()
{
if (HttpMethods.Count == 0)
{
return "All";
}
return string.Join(", ", HttpMethods);
}
public IList<SelectorModel> Selectors { get; }
}
}

View File

@ -6,14 +6,12 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Routing;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
[DebuggerDisplay("Name={ControllerName}, Type={ControllerType.Name}," +
" Routes: {AttributeRoutes.Count}, Filters: {Filters.Count}")]
[DebuggerDisplay("Name={ControllerName}, Type={ControllerType.Name}")]
public class ControllerModel : ICommonModel, IFilterModel, IApiExplorerModel
{
public ControllerModel(
@ -35,12 +33,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
Actions = new List<ActionModel>();
ApiExplorer = new ApiExplorerModel();
Attributes = new List<object>(attributes);
AttributeRoutes = new List<AttributeRouteModel>();
ActionConstraints = new List<IActionConstraintMetadata>();
Filters = new List<IFilterMetadata>();
RouteConstraints = new List<IRouteConstraintProvider>();
Properties = new Dictionary<object, object>();
ControllerProperties = new List<PropertyModel>();
Filters = new List<IFilterMetadata>();
Properties = new Dictionary<object, object>();
RouteConstraints = new List<IRouteConstraintProvider>();
Selectors = new List<SelectorModel>();
}
public ControllerModel(ControllerModel other)
@ -57,7 +54,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
Application = other.Application;
// These are just metadata, safe to create new collections
ActionConstraints = new List<IActionConstraintMetadata>(other.ActionConstraints);
Attributes = new List<object>(other.Attributes);
Filters = new List<IFilterMetadata>(other.Filters);
RouteConstraints = new List<IRouteConstraintProvider>(other.RouteConstraints);
@ -66,15 +62,12 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
// Make a deep copy of other 'model' types.
Actions = new List<ActionModel>(other.Actions.Select(a => new ActionModel(a)));
ApiExplorer = new ApiExplorerModel(other.ApiExplorer);
AttributeRoutes = new List<AttributeRouteModel>(
other.AttributeRoutes.Select(a => new AttributeRouteModel(a)));
ControllerProperties =
new List<PropertyModel>(other.ControllerProperties.Select(p => new PropertyModel(p)));
Selectors = new List<SelectorModel>(other.Selectors.Select(s => new SelectorModel(s)));
}
public IList<IActionConstraintMetadata> ActionConstraints { get; private set; }
public IList<ActionModel> Actions { get; private set; }
public IList<ActionModel> Actions { get; }
/// <summary>
/// Gets or sets the <see cref="ApiExplorerModel"/> for this controller.
@ -82,7 +75,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// <remarks>
/// <see cref="ControllerModel.ApiExplorer"/> allows configuration of settings for ApiExplorer
/// which apply to all actions in the controller unless overridden by <see cref="ActionModel.ApiExplorer"/>.
///
///
/// Settings applied by <see cref="ControllerModel.ApiExplorer"/> override settings from
/// <see cref="ApplicationModel.ApiExplorer"/>.
/// </remarks>
@ -90,8 +83,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public ApplicationModel Application { get; set; }
public IList<AttributeRouteModel> AttributeRoutes { get; private set; }
public IReadOnlyList<object> Attributes { get; }
MemberInfo ICommonModel.MemberInfo => ControllerType;
@ -100,13 +91,13 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public string ControllerName { get; set; }
public TypeInfo ControllerType { get; private set; }
public TypeInfo ControllerType { get; }
public IList<PropertyModel> ControllerProperties { get; }
public IList<IFilterMetadata> Filters { get; private set; }
public IList<IFilterMetadata> Filters { get; }
public IList<IRouteConstraintProvider> RouteConstraints { get; private set; }
public IList<IRouteConstraintProvider> RouteConstraints { get; }
/// <summary>
/// Gets a set of properties associated with the controller.
@ -117,5 +108,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// in <see cref="ApplicationModel.Properties"/>.
/// </remarks>
public IDictionary<object, object> Properties { get; }
public IList<SelectorModel> Selectors { get; }
}
}

View File

@ -0,0 +1,36 @@
// 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;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
public class SelectorModel
{
public SelectorModel()
{
ActionConstraints = new List<IActionConstraintMetadata>();
}
public SelectorModel(SelectorModel other)
{
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
ActionConstraints = new List<IActionConstraintMetadata>(other.ActionConstraints);
if (other.AttributeRouteModel != null)
{
AttributeRouteModel = new AttributeRouteModel(other.AttributeRouteModel);
}
}
public AttributeRouteModel AttributeRouteModel { get; set; }
public IList<IActionConstraintMetadata> ActionConstraints { get; }
}
}

View File

@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing.Tree;
@ -47,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
foreach (var controller in application.Controllers)
{
// Only add properties which are explictly marked to bind.
// Only add properties which are explicitly marked to bind.
// The attribute check is required for ModelBinder attribute.
var controllerPropertyDescriptors = controller.ControllerProperties
.Where(p => p.BindingInfo != null)
@ -128,7 +127,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// 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.
// constraint that prevents them from matching an incoming request.
AddRemovalConstraints(actionDescriptor, removalConstraints);
}
else
@ -185,67 +184,87 @@ namespace Microsoft.AspNetCore.Mvc.Internal
ControllerModel controller,
ActionModel action)
{
var controllerAttributeRoutes = controller.Selectors
.Where(sm => sm.AttributeRouteModel != null)
.Select(sm => sm.AttributeRouteModel)
.ToList();
var actionDescriptors = new List<ControllerActionDescriptor>();
// We check the action to see if the template allows combination behavior
// (It doesn't start with / or ~/) so that in the case where we have multiple
// [Route] attributes on the controller we don't end up creating multiple
if (action.AttributeRouteModel != null &&
action.AttributeRouteModel.IsAbsoluteTemplate)
foreach (var actionSelectorModel in action.Selectors)
{
// We're overriding the attribute routes on the controller, so filter out any metadata
// from controller level routes.
var actionDescriptor = CreateActionDescriptor(
action,
controllerAttributeRoute: null);
var actionAttributeRoute = actionSelectorModel.AttributeRouteModel;
actionDescriptors.Add(actionDescriptor);
// If we're using an attribute route on the controller, then filter out any additional
// metadata from the 'other' attribute routes.
var controllerFilters = controller.Filters
.Where(c => !(c is IRouteTemplateProvider));
AddActionFilters(actionDescriptor, action.Filters, controllerFilters, application.Filters);
var controllerConstraints = controller.ActionConstraints
.Where(c => !(c is IRouteTemplateProvider));
AddActionConstraints(actionDescriptor, action, controllerConstraints);
}
else if (controller.AttributeRoutes != null &&
controller.AttributeRoutes.Count > 0)
{
// We're using the attribute routes from the controller
foreach (var controllerAttributeRoute in controller.AttributeRoutes)
// We check the action to see if the template allows combination behavior
// (It doesn't start with / or ~/) so that in the case where we have multiple
// [Route] attributes on the controller we don't end up creating multiple
if (actionAttributeRoute != null && actionAttributeRoute.IsAbsoluteTemplate)
{
// We're overriding the attribute routes on the controller, so filter out any metadata
// from controller level routes.
var actionDescriptor = CreateActionDescriptor(
action,
controllerAttributeRoute);
actionAttributeRoute,
controllerAttributeRoute: null);
actionDescriptors.Add(actionDescriptor);
AddActionFilters(actionDescriptor, action.Filters, controller.Filters, application.Filters);
// If we're using an attribute route on the controller, then filter out any additional
// metadata from the 'other' attribute routes.
var controllerFilters = controller.Filters
.Where(c => c == controllerAttributeRoute?.Attribute || !(c is IRouteTemplateProvider));
AddActionFilters(actionDescriptor, action.Filters, controllerFilters, application.Filters);
IList<IActionConstraintMetadata> controllerConstraints = null;
if (controller.Selectors.Count > 0)
{
controllerConstraints = controller.Selectors[0].ActionConstraints
.Where(constraint => !(constraint is IRouteTemplateProvider)).ToList();
}
var controllerConstraints = controller.ActionConstraints
.Where(c => c == controllerAttributeRoute?.Attribute || !(c is IRouteTemplateProvider));
AddActionConstraints(actionDescriptor, action, controllerConstraints);
AddActionConstraints(actionDescriptor, action, actionSelectorModel, controllerConstraints);
}
}
else
{
// No attribute routes on the controller
var actionDescriptor = CreateActionDescriptor(
action,
controllerAttributeRoute: null);
actionDescriptors.Add(actionDescriptor);
else if (controllerAttributeRoutes.Count > 0)
{
// We're using the attribute routes from the controller
foreach (var controllerSelectorModel in controller.Selectors)
{
var controllerAttributeRoute = controllerSelectorModel.AttributeRouteModel;
// If there's no attribute route on the controller, then we can use all of the filters/constraints
// on the controller.
AddActionFilters(actionDescriptor, action.Filters, controller.Filters, application.Filters);
AddActionConstraints(actionDescriptor, action, controller.ActionConstraints);
var actionDescriptor = CreateActionDescriptor(
action,
actionAttributeRoute,
controllerAttributeRoute);
actionDescriptors.Add(actionDescriptor);
AddActionFilters(actionDescriptor, action.Filters, controller.Filters, application.Filters);
// If we're using an attribute route on the controller, then filter out any additional
// metadata from the 'other' attribute routes.
var controllerConstraints = controllerSelectorModel.ActionConstraints
.Where(c => c == controllerAttributeRoute?.Attribute || !(c is IRouteTemplateProvider));
AddActionConstraints(actionDescriptor, action, actionSelectorModel, controllerConstraints);
}
}
else
{
// No attribute routes on the controller
var actionDescriptor = CreateActionDescriptor(
action,
actionAttributeRoute,
controllerAttributeRoute: null);
actionDescriptors.Add(actionDescriptor);
IList<IActionConstraintMetadata> controllerConstraints = null;
if (controller.Selectors.Count > 0)
{
controllerConstraints = controller.Selectors[0].ActionConstraints;
}
// If there's no attribute route on the controller, then we use all of the filters/constraints
// on the controller regardless.
AddActionFilters(actionDescriptor, action.Filters, controller.Filters, application.Filters);
AddActionConstraints(actionDescriptor, action, actionSelectorModel, controllerConstraints);
}
}
return actionDescriptors;
@ -253,6 +272,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private static ControllerActionDescriptor CreateActionDescriptor(
ActionModel action,
AttributeRouteModel actionAttributeRoute,
AttributeRouteModel controllerAttributeRoute)
{
var parameterDescriptors = new List<ParameterDescriptor>();
@ -262,17 +282,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterDescriptors.Add(parameterDescriptor);
}
var attributeRouteInfo = CreateAttributeRouteInfo(
action.AttributeRouteModel,
controllerAttributeRoute);
var actionDescriptor = new ControllerActionDescriptor()
{
Name = action.ActionName,
MethodInfo = action.ActionMethod,
Parameters = parameterDescriptors,
RouteConstraints = new List<RouteDataActionConstraint>(),
AttributeRouteInfo = attributeRouteInfo,
AttributeRouteInfo = CreateAttributeRouteInfo(actionAttributeRoute, controllerAttributeRoute)
};
actionDescriptor.DisplayName = string.Format(
@ -316,15 +332,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
ControllerModel controller,
ActionModel action)
{
var isVisible =
action.ApiExplorer?.IsVisible ??
controller.ApiExplorer?.IsVisible ??
var isVisible =
action.ApiExplorer?.IsVisible ??
controller.ApiExplorer?.IsVisible ??
application.ApiExplorer?.IsVisible ??
false;
var isVisibleSetOnActionOrController =
action.ApiExplorer?.IsVisible ??
controller.ApiExplorer?.IsVisible ??
var isVisibleSetOnActionOrController =
action.ApiExplorer?.IsVisible ??
controller.ApiExplorer?.IsVisible ??
false;
// ApiExplorer isn't supported on conventional-routed actions, but we still allow you to configure
@ -417,19 +433,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private static void AddActionConstraints(
ControllerActionDescriptor actionDescriptor,
ActionModel action,
SelectorModel selectorModel,
IEnumerable<IActionConstraintMetadata> controllerConstraints)
{
var constraints = new List<IActionConstraintMetadata>();
var httpMethods = action.HttpMethods;
if (httpMethods != null && httpMethods.Count > 0)
if (selectorModel.ActionConstraints != null)
{
constraints.Add(new HttpMethodActionConstraint(httpMethods));
}
if (action.ActionConstraints != null)
{
constraints.AddRange(action.ActionConstraints);
constraints.AddRange(selectorModel.ActionConstraints);
}
if (controllerConstraints != null)
@ -449,7 +460,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
ControllerModel controller,
ActionModel action)
{
// Apply all the constraints defined on the action, then controller (for example, [Area])
// 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
@ -557,7 +568,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
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.
// and precedence of attribute routes allow this kind of behavior.
if (constraint.KeyHandling == RouteKeyHandling.RequireKey ||
constraint.KeyHandling == RouteKeyHandling.DenyKey)
{

View File

@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.Internal;
@ -50,46 +49,44 @@ namespace Microsoft.AspNetCore.Mvc.Internal
foreach (var controllerType in context.ControllerTypes)
{
var controllerModels = BuildControllerModels(controllerType);
if (controllerModels != null)
var controllerModel = CreateControllerModel(controllerType);
if (controllerModel == null)
{
foreach (var controllerModel in controllerModels)
continue;
}
context.Result.Controllers.Add(controllerModel);
controllerModel.Application = context.Result;
foreach (var propertyHelper in PropertyHelper.GetProperties(controllerType.AsType()))
{
var propertyInfo = propertyHelper.Property;
var propertyModel = CreatePropertyModel(propertyInfo);
if (propertyModel != null)
{
context.Result.Controllers.Add(controllerModel);
controllerModel.Application = context.Result;
propertyModel.Controller = controllerModel;
controllerModel.ControllerProperties.Add(propertyModel);
}
}
foreach (var propertyHelper in PropertyHelper.GetProperties(controllerType.AsType()))
foreach (var methodInfo in controllerType.AsType().GetMethods())
{
var actionModel = CreateActionModel(controllerType, methodInfo);
if (actionModel == null)
{
continue;
}
actionModel.Controller = controllerModel;
controllerModel.Actions.Add(actionModel);
foreach (var parameterInfo in actionModel.ActionMethod.GetParameters())
{
var parameterModel = CreateParameterModel(parameterInfo);
if (parameterModel != null)
{
var propertyInfo = propertyHelper.Property;
var propertyModel = CreatePropertyModel(propertyInfo);
if (propertyModel != null)
{
propertyModel.Controller = controllerModel;
controllerModel.ControllerProperties.Add(propertyModel);
}
}
foreach (var methodInfo in controllerType.AsType().GetMethods())
{
var actionModels = BuildActionModels(controllerType, methodInfo);
if (actionModels != null)
{
foreach (var actionModel in actionModels)
{
actionModel.Controller = controllerModel;
controllerModel.Actions.Add(actionModel);
foreach (var parameterInfo in actionModel.ActionMethod.GetParameters())
{
var parameterModel = CreateParameterModel(parameterInfo);
if (parameterModel != null)
{
parameterModel.Action = actionModel;
actionModel.Parameters.Add(parameterModel);
}
}
}
}
parameterModel.Action = actionModel;
actionModel.Parameters.Add(parameterModel);
}
}
}
@ -102,25 +99,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Intentionally empty.
}
/// <summary>
/// Creates the <see cref="ControllerModel"/> instances for the given controller <see cref="TypeInfo"/>.
/// </summary>
/// <param name="typeInfo">The controller <see cref="TypeInfo"/>.</param>
/// <returns>
/// A set of <see cref="ControllerModel"/> instances for the given controller <see cref="TypeInfo"/> or
/// <c>null</c> if the <paramref name="typeInfo"/> does not represent a controller.
/// </returns>
protected virtual IEnumerable<ControllerModel> BuildControllerModels(TypeInfo typeInfo)
{
if (typeInfo == null)
{
throw new ArgumentNullException(nameof(typeInfo));
}
var controllerModel = CreateControllerModel(typeInfo);
yield return controllerModel;
}
/// <summary>
/// Creates a <see cref="ControllerModel"/> for the given <see cref="TypeInfo"/>.
/// </summary>
@ -177,20 +155,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
filteredAttributes.Add(attribute);
}
}
filteredAttributes.AddRange(routeAttributes);
attributes = filteredAttributes.ToArray();
var controllerModel = new ControllerModel(typeInfo, attributes);
AddRange(
controllerModel.AttributeRoutes, routeAttributes.Select(a => new AttributeRouteModel(a)));
AddRange(controllerModel.Selectors, CreateSelectors(attributes));
controllerModel.ControllerName =
typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ?
typeInfo.Name.Substring(0, typeInfo.Name.Length - "Controller".Length) :
typeInfo.Name;
AddRange(controllerModel.ActionConstraints, attributes.OfType<IActionConstraintMetadata>());
AddRange(controllerModel.Filters, attributes.OfType<IFilterMetadata>());
AddRange(controllerModel.RouteConstraints, attributes.OfType<IRouteConstraintProvider>());
@ -250,15 +228,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
/// <summary>
/// Creates the <see cref="ControllerModel"/> instances for the given action <see cref="MethodInfo"/>.
/// Creates the <see cref="ActionModel"/> instance for the given action <see cref="MethodInfo"/>.
/// </summary>
/// <param name="typeInfo">The controller <see cref="TypeInfo"/>.</param>
/// <param name="methodInfo">The action <see cref="MethodInfo"/>.</param>
/// <returns>
/// A set of <see cref="ActionModel"/> instances for the given action <see cref="MethodInfo"/> or
/// An <see cref="ActionModel"/> instance for the given action <see cref="MethodInfo"/> or
/// <c>null</c> if the <paramref name="methodInfo"/> does not represent an action.
/// </returns>
protected virtual IEnumerable<ActionModel> BuildActionModels(
protected virtual ActionModel CreateActionModel(
TypeInfo typeInfo,
MethodInfo methodInfo)
{
@ -274,9 +252,44 @@ namespace Microsoft.AspNetCore.Mvc.Internal
if (!IsAction(typeInfo, methodInfo))
{
return Enumerable.Empty<ActionModel>();
return null;
}
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
// is needed to so that the result of ToArray() is object
var attributes = methodInfo.GetCustomAttributes(inherit: true).OfType<object>().ToArray();
var actionModel = new ActionModel(methodInfo, attributes);
AddRange(actionModel.Filters, attributes.OfType<IFilterMetadata>());
var actionName = attributes.OfType<ActionNameAttribute>().FirstOrDefault();
if (actionName?.Name != null)
{
actionModel.ActionName = actionName.Name;
}
else
{
actionModel.ActionName = methodInfo.Name;
}
var apiVisibility = attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault();
if (apiVisibility != null)
{
actionModel.ApiExplorer.IsVisible = !apiVisibility.IgnoreApi;
}
var apiGroupName = attributes.OfType<IApiDescriptionGroupNameProvider>().FirstOrDefault();
if (apiGroupName != null)
{
actionModel.ApiExplorer.GroupName = apiGroupName.GroupName;
}
AddRange(actionModel.RouteConstraints, attributes.OfType<IRouteConstraintProvider>());
//TODO: modify comment
// Now we need to determine the action selection info (cross-section of routes and constraints)
//
// For attribute routes on a action, we want want to support 'overriding' routes on a
// virtual method, but allow 'overriding'. So we need to walk up the hierarchy looking
// for the first definition to define routes.
@ -309,10 +322,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
currentMethodInfo = nextMethodInfo;
}
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
// is needed to so that the result of ToArray() is object
var attributes = methodInfo.GetCustomAttributes(inherit: true).OfType<object>().ToArray();
// This is fairly complicated so that we maintain referential equality between items in
// ActionModel.Attributes and ActionModel.Attributes[*].Attribute.
var applicableAttributes = new List<object>();
@ -327,134 +336,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
applicableAttributes.Add(attribute);
}
}
applicableAttributes.AddRange(routeAttributes);
AddRange(actionModel.Selectors, CreateSelectors(applicableAttributes));
attributes = applicableAttributes.ToArray();
// Route attributes create multiple actions, we want to split the set of
// attributes based on these so each action only has the attributes that affect it.
//
// The set of route attributes are split into those that 'define' a route versus those that are
// 'silent'.
//
// We need to define an action for each attribute that 'defines' a route, and a single action
// for all of the ones that don't (if any exist).
//
// If the attribute that 'defines' a route is NOT an IActionHttpMethodProvider, then we'll include with
// it, any IActionHttpMethodProvider that are 'silent' IRouteTemplateProviders. In this case the 'extra'
// action for silent route providers isn't needed.
//
// Ex:
// [HttpGet]
// [AcceptVerbs("POST", "PUT")]
// [HttpPost("Api/Things")]
// public void DoThing()
//
// This will generate 2 actions:
// 1. [HttpPost("Api/Things")]
// 2. [HttpGet], [AcceptVerbs("POST", "PUT")]
//
// Note that having a route attribute that doesn't define a route template _might_ be an error. We
// don't have enough context to really know at this point so we just pass it on.
var routeProviders = new List<object>();
var createActionForSilentRouteProviders = false;
foreach (var attribute in attributes)
{
var routeTemplateProvider = attribute as IRouteTemplateProvider;
if (routeTemplateProvider != null)
{
if (IsSilentRouteAttribute(routeTemplateProvider))
{
createActionForSilentRouteProviders = true;
}
else
{
routeProviders.Add(attribute);
}
}
}
foreach (var routeProvider in routeProviders)
{
// If we see an attribute like
// [Route(...)]
//
// Then we want to group any attributes like [HttpGet] with it.
//
// Basically...
//
// [HttpGet]
// [HttpPost("Products")]
// public void Foo() { }
//
// Is two actions. And...
//
// [HttpGet]
// [Route("Products")]
// public void Foo() { }
//
// Is one action.
if (!(routeProvider is IActionHttpMethodProvider))
{
createActionForSilentRouteProviders = false;
}
}
var actionModels = new List<ActionModel>();
if (routeProviders.Count == 0 && !createActionForSilentRouteProviders)
{
actionModels.Add(CreateActionModel(methodInfo, attributes));
}
else
{
// Each of these routeProviders are the ones that actually have routing information on them
// something like [HttpGet] won't show up here, but [HttpGet("Products")] will.
foreach (var routeProvider in routeProviders)
{
var filteredAttributes = new List<object>();
foreach (var attribute in attributes)
{
if (attribute == routeProvider)
{
filteredAttributes.Add(attribute);
}
else if (routeProviders.Contains(attribute))
{
// Exclude other route template providers
}
else if (
routeProvider is IActionHttpMethodProvider &&
attribute is IActionHttpMethodProvider)
{
// Exclude other http method providers if this route is an
// http method provider.
}
else
{
filteredAttributes.Add(attribute);
}
}
actionModels.Add(CreateActionModel(methodInfo, filteredAttributes));
}
if (createActionForSilentRouteProviders)
{
var filteredAttributes = new List<object>();
foreach (var attribute in attributes)
{
if (!routeProviders.Contains(attribute))
{
filteredAttributes.Add(attribute);
}
}
actionModels.Add(CreateActionModel(methodInfo, filteredAttributes));
}
}
return actionModels;
return actionModel;
}
/// <summary>
@ -525,83 +411,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return methodInfo.IsPublic;
}
/// <summary>
/// Creates an <see cref="ActionModel"/> for the given <see cref="MethodInfo"/>.
/// </summary>
/// <param name="methodInfo">The <see cref="MethodInfo"/>.</param>
/// <param name="attributes">The set of attributes to use as metadata.</param>
/// <returns>An <see cref="ActionModel"/> for the given <see cref="MethodInfo"/>.</returns>
/// <remarks>
/// An action-method in code may expand into multiple <see cref="ActionModel"/> instances depending on how
/// the action is routed. In the case of multiple routing attributes, this method will invoked be once for
/// each action that can be created.
///
/// If overriding this method, use the provided <paramref name="attributes"/> list to find metadata related to
/// the action being created.
/// </remarks>
protected virtual ActionModel CreateActionModel(
MethodInfo methodInfo,
IReadOnlyList<object> attributes)
{
if (methodInfo == null)
{
throw new ArgumentNullException(nameof(methodInfo));
}
if (attributes == null)
{
throw new ArgumentNullException(nameof(attributes));
}
var actionModel = new ActionModel(methodInfo, attributes);
AddRange(actionModel.ActionConstraints, attributes.OfType<IActionConstraintMetadata>());
AddRange(actionModel.Filters, attributes.OfType<IFilterMetadata>());
var actionName = attributes.OfType<ActionNameAttribute>().FirstOrDefault();
if (actionName?.Name != null)
{
actionModel.ActionName = actionName.Name;
}
else
{
actionModel.ActionName = methodInfo.Name;
}
var apiVisibility = attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault();
if (apiVisibility != null)
{
actionModel.ApiExplorer.IsVisible = !apiVisibility.IgnoreApi;
}
var apiGroupName = attributes.OfType<IApiDescriptionGroupNameProvider>().FirstOrDefault();
if (apiGroupName != null)
{
actionModel.ApiExplorer.GroupName = apiGroupName.GroupName;
}
var httpMethods = attributes.OfType<IActionHttpMethodProvider>();
AddRange(actionModel.HttpMethods,
httpMethods
.Where(a => a.HttpMethods != null)
.SelectMany(a => a.HttpMethods)
.Distinct());
AddRange(actionModel.RouteConstraints, attributes.OfType<IRouteConstraintProvider>());
var routeTemplateProvider =
attributes
.OfType<IRouteTemplateProvider>()
.SingleOrDefault(a => !IsSilentRouteAttribute(a));
if (routeTemplateProvider != null)
{
actionModel.AttributeRouteModel = new AttributeRouteModel(routeTemplateProvider);
}
return actionModel;
}
/// <summary>
/// Creates a <see cref="ParameterModel"/> for the given <see cref="ParameterInfo"/>.
/// </summary>
@ -627,6 +436,160 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return parameterModel;
}
private IList<SelectorModel> CreateSelectors(IList<object> attributes)
{
// Route attributes create multiple selector models, we want to split the set of
// attributes based on these so each selector only has the attributes that affect it.
//
// The set of route attributes are split into those that 'define' a route versus those that are
// 'silent'.
//
// We need to define a selector for each attribute that 'defines' a route, and a single selector
// for all of the ones that don't (if any exist).
//
// If the attribute that 'defines' a route is NOT an IActionHttpMethodProvider, then we'll include with
// it, any IActionHttpMethodProvider that are 'silent' IRouteTemplateProviders. In this case the 'extra'
// action for silent route providers isn't needed.
//
// Ex:
// [HttpGet]
// [AcceptVerbs("POST", "PUT")]
// [HttpPost("Api/Things")]
// public void DoThing()
//
// This will generate 2 selectors:
// 1. [HttpPost("Api/Things")]
// 2. [HttpGet], [AcceptVerbs("POST", "PUT")]
//
// Note that having a route attribute that doesn't define a route template _might_ be an error. We
// don't have enough context to really know at this point so we just pass it on.
var routeProviders = new List<IRouteTemplateProvider>();
var createSelectorForSilentRouteProviders = false;
foreach (var attribute in attributes)
{
var routeTemplateProvider = attribute as IRouteTemplateProvider;
if (routeTemplateProvider != null)
{
if (IsSilentRouteAttribute(routeTemplateProvider))
{
createSelectorForSilentRouteProviders = true;
}
else
{
routeProviders.Add(routeTemplateProvider);
}
}
}
foreach (var routeProvider in routeProviders)
{
// If we see an attribute like
// [Route(...)]
//
// Then we want to group any attributes like [HttpGet] with it.
//
// Basically...
//
// [HttpGet]
// [HttpPost("Products")]
// public void Foo() { }
//
// Is two selectors. And...
//
// [HttpGet]
// [Route("Products")]
// public void Foo() { }
//
// Is one selector.
if (!(routeProvider is IActionHttpMethodProvider))
{
createSelectorForSilentRouteProviders = false;
}
}
var selectorModels = new List<SelectorModel>();
if (routeProviders.Count == 0 && !createSelectorForSilentRouteProviders)
{
// Simple case, all attributes apply
selectorModels.Add(CreateSelectorModel(route: null, attributes: attributes));
}
else
{
// Each of these routeProviders are the ones that actually have routing information on them
// something like [HttpGet] won't show up here, but [HttpGet("Products")] will.
foreach (var routeProvider in routeProviders)
{
var filteredAttributes = new List<object>();
foreach (var attribute in attributes)
{
if (attribute == routeProvider)
{
filteredAttributes.Add(attribute);
}
else if (routeProviders.Contains(attribute))
{
// Exclude other route template providers
}
else if (
routeProvider is IActionHttpMethodProvider &&
attribute is IActionHttpMethodProvider)
{
// Exclude other http method providers if this route is an
// http method provider.
}
else
{
filteredAttributes.Add(attribute);
}
}
selectorModels.Add(CreateSelectorModel(routeProvider, filteredAttributes));
}
if (createSelectorForSilentRouteProviders)
{
var filteredAttributes = new List<object>();
foreach (var attribute in attributes)
{
if (!routeProviders.Contains(attribute))
{
filteredAttributes.Add(attribute);
}
}
selectorModels.Add(CreateSelectorModel(route: null, attributes: filteredAttributes));
}
}
return selectorModels;
}
private static SelectorModel CreateSelectorModel(IRouteTemplateProvider route, IList<object> attributes)
{
var selectorModel = new SelectorModel();
if (route != null)
{
selectorModel.AttributeRouteModel = new AttributeRouteModel(route);
}
AddRange(selectorModel.ActionConstraints, attributes.OfType<IActionConstraintMetadata>());
// Simple case, all HTTP method attributes apply
var httpMethods = attributes
.OfType<IActionHttpMethodProvider>()
.SelectMany(a => a.HttpMethods)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
if (httpMethods.Length > 0)
{
selectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(httpMethods));
}
return selectorModel;
}
private bool IsIDisposableMethod(MethodInfo methodInfo, TypeInfo typeInfo)
{
return

View File

@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Routing;
namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
@ -64,34 +64,55 @@ namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
private bool IsActionAttributeRouted(ActionModel action)
{
if (action.Controller.AttributeRoutes.Count > 0)
foreach (var controllerSelectorModel in action.Controller.Selectors)
{
return true;
if (controllerSelectorModel.AttributeRouteModel?.Template != null)
{
return true;
}
}
return action.AttributeRouteModel?.Template != null;
foreach (var actionSelectorModel in action.Selectors)
{
if (actionSelectorModel.AttributeRouteModel?.Template != null)
{
return true;
}
}
return false;
}
private void SetHttpMethodFromConvention(ActionModel action)
{
if (action.HttpMethods.Count > 0)
foreach (var selector in action.Selectors)
{
// If the HttpMethods are set from attributes, don't override it with the convention
return;
if (selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Count() > 0)
{
// If the HttpMethods are set from attributes, don't override it with the convention
return;
}
}
// The Method name is used to infer verb constraints. Changing the action name has not impact.
// The Method name is used to infer verb constraints. Changing the action name has no impact.
foreach (var verb in SupportedHttpMethodConventions)
{
if (action.ActionMethod.Name.StartsWith(verb, StringComparison.OrdinalIgnoreCase))
{
action.HttpMethods.Add(verb);
foreach (var selector in action.Selectors)
{
selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { verb }));
}
return;
}
}
// If no convention matches, then assume POST
action.HttpMethods.Add("POST");
foreach (var actionSelectorModel in action.Selectors)
{
actionSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { "POST" }));
}
}
private class UnnamedActionRouteConstraint : IRouteConstraintProvider

View File

@ -18,7 +18,10 @@ namespace Microsoft.AspNetCore.Mvc.WebApiCompatShim
if (IsConventionApplicable(action.Controller))
{
action.ActionConstraints.Add(new OverloadActionConstraint());
foreach (var actionSelectorModel in action.Selectors)
{
actionSelectorModel.ActionConstraints.Add(new OverloadActionConstraint());
}
}
}

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public void CopyConstructor_DoesDeepCopyOfOtherModels()
{
// Arrange
var action = new ActionModel(typeof(TestController).GetMethod("Edit"),
var action = new ActionModel(typeof(TestController).GetMethod(nameof(TestController.Edit)),
new List<object>());
var parameter = new ParameterModel(action.ActionMethod.GetParameters()[0],
@ -26,7 +26,10 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
action.Parameters.Add(parameter);
var route = new AttributeRouteModel(new HttpGetAttribute("api/Products"));
action.AttributeRouteModel = route;
action.Selectors.Add(new SelectorModel()
{
AttributeRouteModel = route
});
var apiExplorer = action.ApiExplorer;
apiExplorer.IsVisible = false;
@ -38,7 +41,10 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
// Assert
Assert.NotSame(action, action2.Parameters[0]);
Assert.NotSame(apiExplorer, action2.ApiExplorer);
Assert.NotSame(route, action2.AttributeRouteModel);
Assert.NotSame(action.Selectors, action2.Selectors);
Assert.NotNull(action2.Selectors);
Assert.Single(action2.Selectors);
Assert.NotSame(route, action2.Selectors[0].AttributeRouteModel);
}
[Fact]
@ -53,13 +59,14 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
new MyFilterAttribute(),
});
action.ActionConstraints.Add(new HttpMethodActionConstraint(new string[] { "GET" }));
var selectorModel = new SelectorModel();
selectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new string[] { "GET" }));
action.Selectors.Add(selectorModel);
action.ActionName = "Edit";
action.Controller = new ControllerModel(typeof(TestController).GetTypeInfo(),
new List<object>());
action.Filters.Add(new MyFilterAttribute());
action.HttpMethods.Add("GET");
action.RouteConstraints.Add(new MyRouteConstraintAttribute());
action.Properties.Add(new KeyValuePair<object, object>("test key", "test value"));
@ -71,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
// Reflection is used to make sure the test fails when a new property is added.
if (property.Name.Equals("ApiExplorer") ||
property.Name.Equals("AttributeRouteModel") ||
property.Name.Equals("Selectors") ||
property.Name.Equals("Parameters"))
{
// This test excludes other ApplicationModel objects on purpose because we deep copy them.

View File

@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
action.Controller = controller;
var route = new AttributeRouteModel(new HttpGetAttribute("api/Products"));
controller.AttributeRoutes.Add(route);
controller.Selectors.Add(new SelectorModel() { AttributeRouteModel = route });
var apiExplorer = controller.ApiExplorer;
controller.ApiExplorer.GroupName = "group";
@ -39,10 +39,12 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
// Assert
Assert.NotSame(action, controller2.Actions[0]);
Assert.NotSame(route, controller2.AttributeRoutes[0]);
Assert.NotNull(controller2.Selectors);
Assert.Single(controller2.Selectors);
Assert.NotSame(route, controller2.Selectors[0].AttributeRouteModel);
Assert.NotSame(apiExplorer, controller2.ApiExplorer);
Assert.NotSame(controller.ActionConstraints, controller2.ActionConstraints);
Assert.NotSame(controller.Selectors[0].ActionConstraints, controller2.Selectors[0].ActionConstraints);
Assert.NotSame(controller.Actions, controller2.Actions);
Assert.NotSame(controller.Attributes, controller2.Attributes);
Assert.NotSame(controller.Filters, controller2.Filters);
@ -61,7 +63,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
new MyFilterAttribute(),
});
controller.ActionConstraints.Add(new HttpMethodActionConstraint(new string[] { "GET" }));
var selectorModel = new SelectorModel();
selectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new string[] { "GET" }));
controller.Selectors.Add(selectorModel);
controller.Application = new ApplicationModel();
controller.ControllerName = "cool";
controller.Filters.Add(new MyFilterAttribute());
@ -77,7 +81,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
foreach (var property in typeof(ControllerModel).GetProperties())
{
if (property.Name.Equals("Actions") ||
property.Name.Equals("AttributeRoutes") ||
property.Name.Equals("Selectors") ||
property.Name.Equals("ApiExplorer") ||
property.Name.Equals("ControllerProperties"))
{

View File

@ -51,7 +51,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var controller = Assert.Single(context.Result.Controllers);
var action = Assert.Single(controller.Actions);
Assert.Equal("Authorize", action.ActionName);
Assert.Null(action.AttributeRouteModel);
var attributeRoutes = action.Selectors.Where(sm => sm.AttributeRouteModel != null);
Assert.Empty(attributeRoutes);
var authorizeFilters = action.Filters.OfType<AuthorizeFilter>();
Assert.Single(authorizeFilters);
Assert.Equal(3, authorizeFilters.First().Policy.Requirements.Count);

View File

@ -40,8 +40,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
controller.Application = applicationModel;
applicationModel.Controllers.Add(controller);
var methodInfo = typeof(TestController).GetMethod("SomeAction");
var methodInfo = typeof(TestController).GetMethod(nameof(TestController.SomeAction));
var actionModel = new ActionModel(methodInfo, new List<object>() { });
actionModel.Selectors.Add(new SelectorModel());
actionModel.Controller = controller;
controller.Actions.Add(actionModel);
@ -71,8 +72,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
controller.Application = applicationModel;
applicationModel.Controllers.Add(controller);
var methodInfo = typeof(TestController).GetMethod("SomeAction");
var methodInfo = typeof(TestController).GetMethod(nameof(TestController.SomeAction));
var actionModel = new ActionModel(methodInfo, new List<object>() { });
actionModel.Selectors.Add(new SelectorModel());
actionModel.Controller = controller;
controller.Actions.Add(actionModel);
@ -96,8 +98,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
controller.Properties["test"] = "controller";
applicationModel.Controllers.Add(controller);
var methodInfo = typeof(TestController).GetMethod("SomeAction");
var methodInfo = typeof(TestController).GetMethod(nameof(TestController.SomeAction));
var actionModel = new ActionModel(methodInfo, new List<object>() { });
actionModel.Selectors.Add(new SelectorModel());
actionModel.Controller = controller;
controller.Actions.Add(actionModel);
@ -121,8 +124,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
controller.Properties["test"] = "controller";
applicationModel.Controllers.Add(controller);
var methodInfo = typeof(TestController).GetMethod("SomeAction");
var methodInfo = typeof(TestController).GetMethod(nameof(TestController.SomeAction));
var actionModel = new ActionModel(methodInfo, new List<object>() { });
actionModel.Selectors.Add(new SelectorModel());
actionModel.Controller = controller;
actionModel.Properties["test"] = "action";
controller.Actions.Add(actionModel);

View File

@ -416,13 +416,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var conventional = Assert.Single(model.Controllers,
c => c.ControllerName == "ConventionallyRouted");
Assert.Empty(conventional.AttributeRoutes);
Assert.Empty(conventional.Selectors.Where(sm => sm.AttributeRouteModel != null));
Assert.Single(conventional.Actions);
var attributeRouted = Assert.Single(model.Controllers,
c => c.ControllerName == "AttributeRouted");
Assert.Single(attributeRouted.Actions);
Assert.Single(attributeRouted.AttributeRoutes);
Assert.Single(attributeRouted.Selectors.Where(sm => sm.AttributeRouteModel != null));
var empty = Assert.Single(model.Controllers,
c => c.ControllerName == "Empty");
@ -449,10 +449,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Equal(2, controller.Actions.Count);
var getPerson = Assert.Single(controller.Actions, a => a.ActionName == "GetPerson");
Assert.Empty(getPerson.HttpMethods);
Assert.Empty(getPerson.Selectors[0].ActionConstraints.OfType<HttpMethodActionConstraint>());
var showPeople = Assert.Single(controller.Actions, a => a.ActionName == "ShowPeople");
Assert.Empty(showPeople.HttpMethods);
Assert.Empty(showPeople.Selectors[0].ActionConstraints.OfType<HttpMethodActionConstraint>());
}
[Fact]
@ -486,11 +486,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Assert
var controller = Assert.Single(model.Controllers);
var attributeRouteModel = Assert.Single(controller.AttributeRoutes);
Assert.Equal("api/Token/[key]/[controller]", attributeRouteModel.Template);
var selectorModel = Assert.Single(controller.Selectors.Where(sm => sm.AttributeRouteModel != null));
Assert.Equal("api/Token/[key]/[controller]", selectorModel.AttributeRouteModel.Template);
var action = Assert.Single(controller.Actions);
Assert.Equal("stub/[action]", action.AttributeRouteModel.Template);
var actionSelectorModel = Assert.Single(action.Selectors.Where(sm => sm.AttributeRouteModel != null));
Assert.Equal("stub/[action]", actionSelectorModel.AttributeRouteModel.Template);
}
[Fact]
@ -567,7 +568,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var descriptors = provider.GetDescriptors();
// Assert
var actions = descriptors.Where(d => d.Name == "AcceptVerbs");
var actions = descriptors.Where(d => d.Name == nameof(MultiRouteAttributesController.AcceptVerbs));
Assert.Equal(2, actions.Count());
foreach (var action in actions)
@ -1300,28 +1301,26 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var model = provider.BuildModel();
// Assert
var actions = Assert.Single(model.Controllers).Actions;
Assert.Equal(2, actions.Count());
var controllerModel = Assert.Single(model.Controllers);
var actionModel = Assert.Single(controllerModel.Actions);
Assert.Equal(3, actionModel.Attributes.Count);
Assert.Equal(2, actionModel.Attributes.OfType<RouteAndConstraintAttribute>().Count());
Assert.Single(actionModel.Attributes.OfType<ConstraintAttribute>());
Assert.Equal(2, actionModel.Selectors.Count);
var action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "R1");
var selectorModel = Assert.Single(
actionModel.Selectors.Where(sm => sm.AttributeRouteModel?.Template == "R1"));
Assert.Equal(2, action.Attributes.Count);
Assert.Single(action.Attributes, a => a is RouteAndConstraintAttribute);
Assert.Single(action.Attributes, a => a is ConstraintAttribute);
Assert.Equal(2, selectorModel.ActionConstraints.Count);
Assert.Single(selectorModel.ActionConstraints.OfType<RouteAndConstraintAttribute>());
Assert.Single(selectorModel.ActionConstraints.OfType<ConstraintAttribute>());
Assert.Equal(2, action.ActionConstraints.Count);
Assert.Single(action.ActionConstraints, a => a is RouteAndConstraintAttribute);
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
selectorModel = Assert.Single(
actionModel.Selectors.Where(sm => sm.AttributeRouteModel?.Template == "R2"));
action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "R2");
Assert.Equal(2, action.Attributes.Count);
Assert.Single(action.Attributes, a => a is RouteAndConstraintAttribute);
Assert.Single(action.Attributes, a => a is ConstraintAttribute);
Assert.Equal(2, action.ActionConstraints.Count);
Assert.Single(action.ActionConstraints, a => a is RouteAndConstraintAttribute);
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
Assert.Equal(2, selectorModel.ActionConstraints.Count);
Assert.Single(selectorModel.ActionConstraints.OfType<RouteAndConstraintAttribute>());
Assert.Single(selectorModel.ActionConstraints.OfType<ConstraintAttribute>());
}
[Fact]

View File

@ -8,7 +8,6 @@ using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.Options;
@ -112,13 +111,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var model = builder.CreateControllerModel(typeInfo);
// Assert
Assert.Equal(2, model.AttributeRoutes.Count);
var attributeRoutes = GetAttributeRoutes(model.Selectors);
Assert.Equal(2, attributeRoutes.Count);
Assert.Equal(2, model.Attributes.Count);
var route = Assert.Single(model.AttributeRoutes, r => r.Template == "A");
var route = Assert.Single(attributeRoutes, r => r.Template == "A");
Assert.Contains(route.Attribute, model.Attributes);
route = Assert.Single(model.AttributeRoutes, r => r.Template == "B");
route = Assert.Single(attributeRoutes, r => r.Template == "B");
Assert.Contains(route.Attribute, model.Attributes);
}
@ -133,13 +133,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var model = builder.CreateControllerModel(typeInfo);
// Assert
Assert.Equal(2, model.AttributeRoutes.Count);
var attributeRoutes = GetAttributeRoutes(model.Selectors);
Assert.Equal(2, attributeRoutes.Count);
Assert.Equal(2, model.Attributes.Count);
var route = Assert.Single(model.AttributeRoutes, r => r.Template == "C");
var route = Assert.Single(attributeRoutes, r => r.Template == "C");
Assert.Contains(route.Attribute, model.Attributes);
route = Assert.Single(model.AttributeRoutes, r => r.Template == "D");
route = Assert.Single(attributeRoutes, r => r.Template == "D");
Assert.Contains(route.Attribute, model.Attributes);
}
@ -413,7 +414,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public void BuildActionModels_ConventionallyRoutedAction_WithoutHttpConstraints()
public void CreateActionModel_ConventionallyRoutedAction_WithoutHttpConstraints()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -421,18 +422,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(ConventionallyRoutedController.Edit);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
var action = Assert.Single(actions);
Assert.Equal("Edit", action.ActionName);
Assert.Empty(action.HttpMethods);
Assert.Null(action.AttributeRouteModel);
Assert.NotNull(action);
Assert.Equal(actionName, action.ActionName);
Assert.Empty(action.Attributes);
Assert.Single(action.Selectors);
Assert.Empty(action.Selectors[0].ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Empty(GetAttributeRoutes(action.Selectors));
}
[Fact]
public void BuildActionModels_ConventionallyRoutedAction_WithHttpConstraints()
public void CreateActionModel_ConventionallyRoutedAction_WithHttpConstraints()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -440,20 +442,23 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(ConventionallyRoutedController.Update);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
var action = Assert.Single(actions);
Assert.Contains("PUT", action.HttpMethods);
Assert.Contains("PATCH", action.HttpMethods);
Assert.NotNull(action);
Assert.Single(action.Selectors);
var methodConstraint = Assert.Single(
action.Selectors[0].ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Contains("PUT", methodConstraint.HttpMethods);
Assert.Contains("PATCH", methodConstraint.HttpMethods);
Assert.Equal("Update", action.ActionName);
Assert.Null(action.AttributeRouteModel);
Assert.Equal(actionName, action.ActionName);
Assert.Empty(GetAttributeRoutes(action.Selectors));
Assert.IsType<CustomHttpMethodsAttribute>(Assert.Single(action.Attributes));
}
[Fact]
public void BuildActionModels_ConventionallyRoutedActionWithHttpConstraints_AndInvalidRouteTemplateProvider()
public void CreateActionModel_ConventionallyRoutedActionWithHttpConstraints_AndInvalidRouteTemplateProvider()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -461,21 +466,24 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(ConventionallyRoutedController.Delete);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
var action = Assert.Single(actions);
Assert.Contains("DELETE", action.HttpMethods);
Assert.Contains("HEAD", action.HttpMethods);
Assert.NotNull(action);
Assert.Single(action.Selectors);
var methodConstraint = Assert.Single(
action.Selectors[0].ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Contains("DELETE", methodConstraint.HttpMethods);
Assert.Contains("HEAD", methodConstraint.HttpMethods);
Assert.Equal("Delete", action.ActionName);
Assert.Null(action.AttributeRouteModel);
Assert.Equal(actionName, action.ActionName);
Assert.Empty(GetAttributeRoutes(action.Selectors));
Assert.Single(action.Attributes.OfType<HttpDeleteAttribute>());
Assert.Single(action.Attributes.OfType<HttpHeadAttribute>());
}
[Fact]
public void BuildActionModels_ConventionallyRoutedAction_WithMultipleHttpConstraints()
public void CreateActionModel_ConventionallyRoutedAction_WithMultipleHttpConstraints()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -483,19 +491,22 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(ConventionallyRoutedController.Details);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
var action = Assert.Single(actions);
Assert.Contains("GET", action.HttpMethods);
Assert.Contains("POST", action.HttpMethods);
Assert.Contains("HEAD", action.HttpMethods);
Assert.Equal("Details", action.ActionName);
Assert.Null(action.AttributeRouteModel);
Assert.NotNull(action);
Assert.Single(action.Selectors);
var methodConstraint = Assert.Single(
action.Selectors[0].ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Contains("GET", methodConstraint.HttpMethods);
Assert.Contains("POST", methodConstraint.HttpMethods);
Assert.Contains("HEAD", methodConstraint.HttpMethods);
Assert.Equal(actionName, action.ActionName);
Assert.Empty(GetAttributeRoutes(action.Selectors));
}
[Fact]
public void BuildActionModels_ConventionallyRoutedAction_WithMultipleOverlappingHttpConstraints()
public void CreateActionModel_ConventionallyRoutedAction_WithMultipleOverlappingHttpConstraints()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -503,19 +514,22 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(ConventionallyRoutedController.List);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
var action = Assert.Single(actions);
Assert.Contains("GET", action.HttpMethods);
Assert.Contains("PUT", action.HttpMethods);
Assert.Contains("POST", action.HttpMethods);
Assert.Equal("List", action.ActionName);
Assert.Null(action.AttributeRouteModel);
Assert.NotNull(action);
Assert.Single(action.Selectors);
var methodConstraint = Assert.Single(
action.Selectors[0].ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Contains("GET", methodConstraint.HttpMethods);
Assert.Contains("PUT", methodConstraint.HttpMethods);
Assert.Contains("POST", methodConstraint.HttpMethods);
Assert.Equal(actionName, action.ActionName);
Assert.Empty(GetAttributeRoutes(action.Selectors));
}
[Fact]
public void BuildActionModels_AttributeRouteOnAction()
public void CreateActionModel_AttributeRouteOnAction()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -523,24 +537,27 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(NoRouteAttributeOnControllerController.Edit);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
var action = Assert.Single(actions);
Assert.NotNull(action);
Assert.Single(action.Selectors);
var methodConstraint = Assert.Single(
action.Selectors[0].ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Equal("Edit", action.ActionName);
Assert.Equal(actionName, action.ActionName);
var httpMethod = Assert.Single(action.HttpMethods);
var httpMethod = Assert.Single(methodConstraint.HttpMethods);
Assert.Equal("HEAD", httpMethod);
Assert.NotNull(action.AttributeRouteModel);
Assert.Equal("Change", action.AttributeRouteModel.Template);
var attributeRoute = Assert.Single(GetAttributeRoutes(action.Selectors));
Assert.Equal("Change", attributeRoute.Template);
Assert.IsType<HttpHeadAttribute>(Assert.Single(action.Attributes));
}
[Fact]
public void BuildActionModels_AttributeRouteOnAction_RouteAttribute()
public void CreateActionModel_AttributeRouteOnAction_RouteAttribute()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -548,23 +565,23 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(NoRouteAttributeOnControllerController.Update);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
var action = Assert.Single(actions);
Assert.NotNull(action);
Assert.Single(action.Selectors);
Assert.Empty(action.Selectors[0].ActionConstraints);
Assert.Equal("Update", action.ActionName);
Assert.Equal(actionName, action.ActionName);
Assert.Empty(action.HttpMethods);
Assert.NotNull(action.AttributeRouteModel);
Assert.Equal("Update", action.AttributeRouteModel.Template);
var attributeRoute = Assert.Single(GetAttributeRoutes(action.Selectors));
Assert.Equal("Update", attributeRoute.Template);
Assert.IsType<RouteAttribute>(Assert.Single(action.Attributes));
}
[Fact]
public void BuildActionModels_AttributeRouteOnAction_AcceptVerbsAttributeWithTemplate()
public void CreateActionModel_AttributeRouteOnAction_AcceptVerbsAttributeWithTemplate()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -572,23 +589,28 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(NoRouteAttributeOnControllerController.List);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
var action = Assert.Single(actions);
Assert.NotNull(action);
Assert.Single(action.Selectors);
var methodConstraint = Assert.Single(
action.Selectors[0].ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Equal("List", action.ActionName);
Assert.Equal(actionName, action.ActionName);
Assert.Equal(new[] { "GET", "HEAD" }, action.HttpMethods.OrderBy(m => m, StringComparer.Ordinal));
Assert.Equal(
new[] { "GET", "HEAD" },
methodConstraint.HttpMethods.OrderBy(m => m, StringComparer.Ordinal));
Assert.NotNull(action.AttributeRouteModel);
Assert.Equal("ListAll", action.AttributeRouteModel.Template);
var attributeRoute = Assert.Single(GetAttributeRoutes(action.Selectors));
Assert.Equal("ListAll", attributeRoute.Template);
Assert.IsType<AcceptVerbsAttribute>(Assert.Single(action.Attributes));
}
[Fact]
public void BuildActionModels_AttributeRouteOnAction_CreatesOneActionInforPerRouteTemplate()
public void CreateActionModel_AttributeRouteOnAction_CreatesOneActionInforPerRouteTemplate()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -596,30 +618,35 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(NoRouteAttributeOnControllerController.Index);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
Assert.Equal(2, actions.Count());
Assert.NotNull(action);
Assert.Equal(actionName, action.ActionName);
Assert.NotNull(action.Attributes);
Assert.Equal(2, action.Attributes.Count);
Assert.Single(action.Attributes.OfType<HttpGetAttribute>());
Assert.Single(action.Attributes.OfType<HttpPostAttribute>());
Assert.Equal(2, action.Selectors.Count);
foreach (var action in actions)
foreach (var actionSelectorModel in action.Selectors)
{
Assert.Equal("Index", action.ActionName);
Assert.NotNull(action.AttributeRouteModel);
Assert.NotNull(actionSelectorModel.AttributeRouteModel);
}
var list = Assert.Single(actions, ai => ai.AttributeRouteModel.Template.Equals("List"));
var listMethod = Assert.Single(list.HttpMethods);
var selectorModel = Assert.Single(action.Selectors, ai => ai.AttributeRouteModel?.Template == "List");
var methodConstraint = Assert.Single(selectorModel.ActionConstraints.OfType<HttpMethodActionConstraint>());
var listMethod = Assert.Single(methodConstraint.HttpMethods);
Assert.Equal("POST", listMethod);
Assert.IsType<HttpPostAttribute>(Assert.Single(list.Attributes));
var all = Assert.Single(actions, ai => ai.AttributeRouteModel.Template.Equals("All"));
var allMethod = Assert.Single(all.HttpMethods);
var all = Assert.Single(action.Selectors, ai => ai.AttributeRouteModel?.Template == "All");
methodConstraint = Assert.Single(all.ActionConstraints.OfType<HttpMethodActionConstraint>());
var allMethod = Assert.Single(methodConstraint.HttpMethods);
Assert.Equal("GET", allMethod);
Assert.IsType<HttpGetAttribute>(Assert.Single(all.Attributes));
}
[Fact]
public void BuildActionModels_NoRouteOnController_AllowsConventionallyRoutedActions_OnTheSameController()
public void CreateActionModel_NoRouteOnController_AllowsConventionallyRoutedActions_OnTheSameController()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -627,77 +654,74 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(NoRouteAttributeOnControllerController.Remove);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
var action = Assert.Single(actions);
Assert.Equal("Remove", action.ActionName);
Assert.Empty(action.HttpMethods);
Assert.Null(action.AttributeRouteModel);
Assert.NotNull(action);
Assert.Equal(actionName, action.ActionName);
Assert.Empty(action.Attributes);
Assert.Single(action.Selectors);
Assert.Empty(action.Selectors[0].ActionConstraints);
Assert.Null(action.Selectors[0].AttributeRouteModel);
}
[Theory]
[InlineData(typeof(SingleRouteAttributeController))]
[InlineData(typeof(MultipleRouteAttributeController))]
public void BuildActionModels_RouteAttributeOnController_CreatesAttributeRoute_ForNonAttributedActions(Type controller)
public void CreateActionModel_RouteAttributeOnController_CreatesAttributeRoute_ForNonAttributedActions(Type controller)
{
// Arrange
var builder = new TestApplicationModelProvider();
var typeInfo = controller.GetTypeInfo();
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod("Delete"));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod("Delete"));
// Assert
var action = Assert.Single(actions);
Assert.NotNull(action);
Assert.Equal("Delete", action.ActionName);
Assert.Empty(action.HttpMethods);
Assert.Null(action.AttributeRouteModel);
Assert.Single(action.Selectors);
Assert.Empty(action.Selectors[0].ActionConstraints);
Assert.Empty(GetAttributeRoutes(action.Selectors));
Assert.Empty(action.Attributes);
}
[Theory]
[InlineData(typeof(SingleRouteAttributeController))]
[InlineData(typeof(MultipleRouteAttributeController))]
public void BuildActionModels_RouteOnController_CreatesOneActionInforPerRouteTemplateOnAction(Type controller)
public void CreateActionModel_RouteOnController_CreatesOneActionInforPerRouteTemplateOnAction(Type controller)
{
// Arrange
var builder = new TestApplicationModelProvider();
var typeInfo = controller.GetTypeInfo();
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod("Index"));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod("Index"));
// Assert
Assert.Equal(2, actions.Count());
Assert.NotNull(action.Attributes);
Assert.Equal(2, action.Attributes.Count);
Assert.Equal(2, action.Selectors.Count);
Assert.Equal("Index", action.ActionName);
foreach (var action in actions)
foreach (var selectorModel in action.Selectors)
{
Assert.Equal("Index", action.ActionName);
var httpMethod = Assert.Single(action.HttpMethods);
var methodConstraint = Assert.Single(selectorModel.ActionConstraints.OfType<HttpMethodActionConstraint>());
var httpMethod = Assert.Single(methodConstraint.HttpMethods);
Assert.Equal("GET", httpMethod);
Assert.NotNull(action.AttributeRouteModel.Template);
Assert.IsType<HttpGetAttribute>(Assert.Single(action.Attributes));
Assert.NotNull(selectorModel.AttributeRouteModel.Template);
}
Assert.Single(actions, ai => ai.AttributeRouteModel.Template.Equals("List"));
Assert.Single(actions, ai => ai.AttributeRouteModel.Template.Equals("All"));
Assert.Single(action.Selectors, ai => ai.AttributeRouteModel.Template.Equals("List"));
Assert.Single(action.Selectors, ai => ai.AttributeRouteModel.Template.Equals("All"));
}
[Fact]
public void BuildActionModels_MixedHttpVerbsAndRoutes_EmptyVerbWithRoute()
public void CreateActionModel_MixedHttpVerbsAndRoutes_EmptyVerbWithRoute()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -705,16 +729,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(MixedHttpVerbsAndRouteAttributeController.VerbAndRoute);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
var action = Assert.Single(actions);
Assert.Equal<string>(new string[] { "GET" }, action.HttpMethods);
Assert.Equal("Products", action.AttributeRouteModel.Template);
Assert.NotNull(action);
Assert.Single(action.Selectors);
var methodConstraint = Assert.Single(
action.Selectors[0].ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Equal<string>(new string[] { "GET" }, methodConstraint.HttpMethods);
var attributeRoute = Assert.Single(GetAttributeRoutes(action.Selectors));
Assert.Equal("Products", attributeRoute.Template);
}
[Fact]
public void BuildActionModels_MixedHttpVerbsAndRoutes_MultipleEmptyVerbsWithMultipleRoutes()
public void CreateActionModel_MixedHttpVerbsAndRoutes_MultipleEmptyVerbsWithMultipleRoutes()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -722,21 +750,23 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(MixedHttpVerbsAndRouteAttributeController.MultipleVerbsAndRoutes);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var actions = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
Assert.Equal(2, actions.Count());
Assert.Equal(2, actions.Selectors.Count);
// OrderBy is used because the order of the results may very depending on the platform / client.
var action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "Products");
Assert.Equal(new[] { "GET", "POST" }, action.HttpMethods.OrderBy(key => key, StringComparer.Ordinal));
var selectorModel = Assert.Single(actions.Selectors, a => a.AttributeRouteModel.Template == "Products");
var methodConstraint = Assert.Single(selectorModel.ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Equal(new[] { "GET", "POST" }, methodConstraint.HttpMethods.OrderBy(key => key, StringComparer.Ordinal));
action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "v2/Products");
Assert.Equal(new[] { "GET", "POST" }, action.HttpMethods.OrderBy(key => key, StringComparer.Ordinal));
selectorModel = Assert.Single(actions.Selectors, a => a.AttributeRouteModel.Template == "v2/Products");
methodConstraint = Assert.Single(selectorModel.ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Equal(new[] { "GET", "POST" }, methodConstraint.HttpMethods.OrderBy(key => key, StringComparer.Ordinal));
}
[Fact]
public void BuildActionModels_MixedHttpVerbsAndRoutes_MultipleEmptyAndNonEmptyVerbsWithMultipleRoutes()
public void CreateActionModel_MixedHttpVerbsAndRoutes_MultipleEmptyAndNonEmptyVerbsWithMultipleRoutes()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -744,23 +774,26 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(MixedHttpVerbsAndRouteAttributeController.MultipleVerbsWithAnyWithoutTemplateAndRoutes);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
Assert.Equal(3, actions.Count());
Assert.Equal(3, action.Selectors.Count);
var action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "Products");
Assert.Equal<string>(new string[] { "GET" }, action.HttpMethods);
var selectorModel = Assert.Single(action.Selectors, s => s.AttributeRouteModel.Template == "Products");
var methodConstraint = Assert.Single(selectorModel.ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Equal<string>(new string[] { "GET" }, methodConstraint.HttpMethods);
action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "v2/Products");
Assert.Equal<string>(new string[] { "GET" }, action.HttpMethods);
selectorModel = Assert.Single(action.Selectors, s => s.AttributeRouteModel.Template == "v2/Products");
methodConstraint = Assert.Single(selectorModel.ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Equal<string>(new string[] { "GET" }, methodConstraint.HttpMethods);
action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "Products/Buy");
Assert.Equal<string>(new string[] { "POST" }, action.HttpMethods);
selectorModel = Assert.Single(action.Selectors, s => s.AttributeRouteModel.Template == "Products/Buy");
methodConstraint = Assert.Single(selectorModel.ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Equal<string>(new string[] { "POST" }, methodConstraint.HttpMethods);
}
[Fact]
public void BuildActionModels_MixedHttpVerbsAndRoutes_MultipleEmptyAndNonEmptyVerbs()
public void CreateActionModel_MixedHttpVerbsAndRoutes_MultipleEmptyAndNonEmptyVerbs()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -768,20 +801,23 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(MixedHttpVerbsAndRouteAttributeController.Invalid);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
Assert.Equal(2, actions.Count());
Assert.NotNull(action);
Assert.Equal(2, action.Selectors.Count);
var action = Assert.Single(actions, a => a.AttributeRouteModel?.Template == "Products");
Assert.Equal<string>(new string[] { "POST" }, action.HttpMethods);
var selectorModel = Assert.Single(action.Selectors, s => s.AttributeRouteModel?.Template == "Products");
var methodConstraint = Assert.Single(selectorModel.ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Equal<string>(new string[] { "POST" }, methodConstraint.HttpMethods);
action = Assert.Single(actions, a => a.AttributeRouteModel?.Template == null);
Assert.Equal<string>(new string[] { "GET" }, action.HttpMethods);
selectorModel = Assert.Single(action.Selectors, s => s.AttributeRouteModel?.Template == null);
methodConstraint = Assert.Single(selectorModel.ActionConstraints.OfType<HttpMethodActionConstraint>());
Assert.Equal<string>(new string[] { "GET" }, methodConstraint.HttpMethods);
}
[Fact]
public void BuildActionModels_InheritedAttributeRoutes()
public void CreateActionModel_InheritedAttributeRoutes()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -789,22 +825,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(DerivedClassInheritsAttributeRoutesController.Edit);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var actions = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
Assert.Equal(2, actions.Count());
Assert.Equal(2, actions.Attributes.Count);
Assert.Equal(2, actions.Selectors.Count);
var action = Assert.Single(actions, a => a.AttributeRouteModel?.Template == "A");
Assert.Equal(1, action.Attributes.Count);
Assert.Contains(action.AttributeRouteModel.Attribute, action.Attributes);
var selectorModel = Assert.Single(actions.Selectors, a => a.AttributeRouteModel?.Template == "A");
Assert.Contains(selectorModel.AttributeRouteModel.Attribute, actions.Attributes);
action = Assert.Single(actions, a => a.AttributeRouteModel?.Template == "B");
Assert.Equal(1, action.Attributes.Count);
Assert.Contains(action.AttributeRouteModel.Attribute, action.Attributes);
selectorModel = Assert.Single(actions.Selectors, a => a.AttributeRouteModel?.Template == "B");
Assert.Contains(selectorModel.AttributeRouteModel.Attribute, actions.Attributes);
}
[Fact]
public void BuildActionModels_InheritedAttributeRoutesOverridden()
public void CreateActionModel_InheritedAttributeRoutesOverridden()
{
// Arrange
var builder = new TestApplicationModelProvider();
@ -812,18 +847,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var actionName = nameof(DerivedClassOverridesAttributeRoutesController.Edit);
// Act
var actions = builder.BuildActionModels(typeInfo, typeInfo.AsType().GetMethod(actionName));
var action = builder.CreateActionModel(typeInfo, typeInfo.AsType().GetMethod(actionName));
// Assert
Assert.Equal(2, actions.Count());
Assert.Equal(4, action.Attributes.Count);
Assert.Equal(2, action.Selectors.Count);
var action = Assert.Single(actions, a => a.AttributeRouteModel?.Template == "C");
Assert.Equal(1, action.Attributes.Count);
Assert.Contains(action.AttributeRouteModel.Attribute, action.Attributes);
var selectorModel = Assert.Single(action.Selectors, a => a.AttributeRouteModel?.Template == "C");
Assert.Contains(selectorModel.AttributeRouteModel.Attribute, action.Attributes);
action = Assert.Single(actions, a => a.AttributeRouteModel?.Template == "D");
Assert.Equal(1, action.Attributes.Count);
Assert.Contains(action.AttributeRouteModel.Attribute, action.Attributes);
selectorModel = Assert.Single(action.Selectors, a => a.AttributeRouteModel?.Template == "D");
Assert.Contains(selectorModel.AttributeRouteModel.Attribute, action.Attributes);
}
private IList<AttributeRouteModel> GetAttributeRoutes(IList<SelectorModel> selectors)
{
return selectors
.Where(sm => sm.AttributeRouteModel != null)
.Select(sm => sm.AttributeRouteModel)
.ToList();
}
private class BaseClassWithAttributeRoutesController
@ -1223,31 +1265,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public MvcOptions Options { get; }
public new IEnumerable<ControllerModel> BuildControllerModels(TypeInfo typeInfo)
{
return base.BuildControllerModels(typeInfo);
}
public new ControllerModel CreateControllerModel(TypeInfo typeInfo)
{
return base.CreateControllerModel(typeInfo);
}
public new ActionModel CreateActionModel(TypeInfo typeInfo, MethodInfo methodInfo)
{
return base.CreateActionModel(typeInfo, methodInfo);
}
public new PropertyModel CreatePropertyModel(PropertyInfo propertyInfo)
{
return base.CreatePropertyModel(propertyInfo);
}
public new IEnumerable<ActionModel> BuildActionModels(TypeInfo typeInfo, MethodInfo methodInfo)
{
return base.BuildActionModels(typeInfo, methodInfo);
}
public new ActionModel CreateActionModel(MethodInfo methodInfo, IReadOnlyList<object> attributes)
{
return base.CreateActionModel(methodInfo, attributes);
}
public new bool IsAction(TypeInfo typeInfo, MethodInfo methodInfo)
{
return base.IsAction(typeInfo, methodInfo);