Refactoring the ControllerActionDescriptorProvider
This commit is contained in:
parent
756f8be49c
commit
7ae5e66ccd
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class ActionInfo
|
||||
{
|
||||
public string ActionName { get; set; }
|
||||
|
||||
public string[] HttpMethods { get; set; }
|
||||
|
||||
public IRouteTemplateProvider AttributeRoute { get; set; }
|
||||
|
||||
public object[] Attributes { get; set; }
|
||||
|
||||
public bool RequireActionNameMatch { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ApplicationModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies conventions to a <see cref="GlobalModel"/>.
|
||||
/// </summary>
|
||||
public static class ApplicationModelConventions
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies conventions to a <see cref="GlobalModel"/>.
|
||||
/// </summary>
|
||||
/// <param name="applicationModel">The <see cref="GlobalModel"/>.</param>
|
||||
/// <param name="conventions">The set of conventions.</param>
|
||||
public static void ApplyConventions(
|
||||
[NotNull] GlobalModel applicationModel,
|
||||
[NotNull] IEnumerable<IGlobalModelConvention> conventions)
|
||||
{
|
||||
// Conventions are applied from the outside-in to allow for scenarios where an action overrides
|
||||
// a controller, etc.
|
||||
foreach (var convention in conventions)
|
||||
{
|
||||
convention.Apply(applicationModel);
|
||||
}
|
||||
|
||||
// First apply the conventions from attributes in decreasing order of scope.
|
||||
foreach (var controller in applicationModel.Controllers)
|
||||
{
|
||||
// ToArray is needed here to prevent issues with modifying the attributes collection
|
||||
// while iterating it.
|
||||
var controllerConventions =
|
||||
controller.Attributes
|
||||
.OfType<IControllerModelConvention>()
|
||||
.ToArray();
|
||||
|
||||
foreach (var controllerConvention in controllerConventions)
|
||||
{
|
||||
controllerConvention.Apply(controller);
|
||||
}
|
||||
|
||||
foreach (var action in controller.Actions)
|
||||
{
|
||||
// ToArray is needed here to prevent issues with modifying the attributes collection
|
||||
// while iterating it.
|
||||
var actionConventions =
|
||||
action.Attributes
|
||||
.OfType<IActionModelConvention>()
|
||||
.ToArray();
|
||||
|
||||
foreach (var actionConvention in actionConventions)
|
||||
{
|
||||
actionConvention.Apply(action);
|
||||
}
|
||||
|
||||
foreach (var parameter in action.Parameters)
|
||||
{
|
||||
// ToArray is needed here to prevent issues with modifying the attributes collection
|
||||
// while iterating it.
|
||||
var parameterConventions =
|
||||
parameter.Attributes
|
||||
.OfType<IParameterModelConvention>()
|
||||
.ToArray();
|
||||
|
||||
foreach (var parameterConvention in parameterConventions)
|
||||
{
|
||||
parameterConvention.Apply(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ApplicationModel
|
||||
{
|
||||
/// <summary>
|
||||
/// A default implementation of <see cref="IActionModelBuilder"/>.
|
||||
/// </summary>
|
||||
public class DefaultActionModelBuilder : IActionModelBuilder
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ActionModel> BuildActionModels([NotNull] MethodInfo methodInfo)
|
||||
{
|
||||
if (!IsAction(methodInfo))
|
||||
{
|
||||
return Enumerable.Empty<ActionModel>();
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
// 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 from action for each attribute that 'defines' a route, and a single action
|
||||
// for all of the ones that don't (if any exist).
|
||||
//
|
||||
// Ex:
|
||||
// [HttpGet]
|
||||
// [AcceptVerbs("POST", "PUT")]
|
||||
// [Route("Api/Things")]
|
||||
// public void DoThing()
|
||||
//
|
||||
// This will generate 2 actions:
|
||||
// 1. [Route("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 splitAttributes = new List<object>();
|
||||
|
||||
var hasSilentRouteAttribute = false;
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
var routeTemplateProvider = attribute as IRouteTemplateProvider;
|
||||
if (routeTemplateProvider != null)
|
||||
{
|
||||
if (IsSilentRouteAttribute(routeTemplateProvider))
|
||||
{
|
||||
hasSilentRouteAttribute = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
splitAttributes.Add(attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var actionModels = new List<ActionModel>();
|
||||
if (splitAttributes.Count == 0 && !hasSilentRouteAttribute)
|
||||
{
|
||||
actionModels.Add(CreateActionModel(methodInfo, attributes));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var splitAttribute in splitAttributes)
|
||||
{
|
||||
var filteredAttributes = new List<object>();
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
if (attribute == splitAttribute)
|
||||
{
|
||||
filteredAttributes.Add(attribute);
|
||||
}
|
||||
else if (attribute is IRouteTemplateProvider)
|
||||
{
|
||||
// Exclude other route template providers
|
||||
}
|
||||
else
|
||||
{
|
||||
filteredAttributes.Add(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
actionModels.Add(CreateActionModel(methodInfo, filteredAttributes));
|
||||
}
|
||||
|
||||
if (hasSilentRouteAttribute)
|
||||
{
|
||||
var filteredAttributes = new List<object>();
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
if (!splitAttributes.Contains(attribute))
|
||||
{
|
||||
filteredAttributes.Add(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
actionModels.Add(CreateActionModel(methodInfo, filteredAttributes));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var actionModel in actionModels)
|
||||
{
|
||||
foreach (var parameterInfo in actionModel.ActionMethod.GetParameters())
|
||||
{
|
||||
var parameterModel = CreateParameterModel(parameterInfo);
|
||||
if (parameterModel != null)
|
||||
{
|
||||
parameterModel.Action = actionModel;
|
||||
actionModel.Parameters.Add(parameterModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actionModels;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if the <paramref name="methodInfo"/> is an action. Otherwise <c>false</c>.
|
||||
/// </summary>
|
||||
/// <param name="methodInfo">The <see cref="MethodInfo"/>.</param>
|
||||
/// <returns><c>true</c> if the <paramref name="methodInfo"/> is an action. Otherwise <c>false</c>.</returns>
|
||||
/// <remarks>
|
||||
/// Override this method to provide custom logic to determine which methods are considered actions.
|
||||
/// </remarks>
|
||||
protected virtual bool IsAction([NotNull] MethodInfo methodInfo)
|
||||
{
|
||||
return
|
||||
methodInfo.IsPublic &&
|
||||
!methodInfo.IsStatic &&
|
||||
!methodInfo.IsAbstract &&
|
||||
!methodInfo.IsConstructor &&
|
||||
!methodInfo.IsGenericMethod &&
|
||||
|
||||
// The SpecialName bit is set to flag members that are treated in a special way by some compilers
|
||||
// (such as property accessors and operator overloading methods).
|
||||
!methodInfo.IsSpecialName &&
|
||||
!methodInfo.IsDefined(typeof(NonActionAttribute)) &&
|
||||
|
||||
// Overriden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid.
|
||||
methodInfo.GetBaseDefinition().DeclaringType != typeof(object);
|
||||
}
|
||||
|
||||
/// <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(
|
||||
[NotNull] MethodInfo methodInfo,
|
||||
[NotNull] IReadOnlyList<object> attributes)
|
||||
{
|
||||
var actionModel = new ActionModel(methodInfo)
|
||||
{
|
||||
IsActionNameMatchRequired = true,
|
||||
};
|
||||
|
||||
actionModel.Attributes.AddRange(attributes);
|
||||
|
||||
actionModel.ActionConstraints.AddRange(attributes.OfType<IActionConstraintMetadata>());
|
||||
actionModel.Filters.AddRange(attributes.OfType<IFilter>());
|
||||
|
||||
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.ApiExplorerIsVisible = !apiVisibility.IgnoreApi;
|
||||
}
|
||||
|
||||
var apiGroupName = attributes.OfType<IApiDescriptionGroupNameProvider>().FirstOrDefault();
|
||||
if (apiGroupName != null)
|
||||
{
|
||||
actionModel.ApiExplorerGroupName = apiGroupName.GroupName;
|
||||
}
|
||||
|
||||
var httpMethods = attributes.OfType<IActionHttpMethodProvider>();
|
||||
actionModel.HttpMethods.AddRange(
|
||||
httpMethods
|
||||
.Where(a => a.HttpMethods != null)
|
||||
.SelectMany(a => a.HttpMethods)
|
||||
.Distinct());
|
||||
|
||||
var routeTemplateProvider = attributes.OfType<IRouteTemplateProvider>().FirstOrDefault();
|
||||
if (routeTemplateProvider != null && !IsSilentRouteAttribute(routeTemplateProvider))
|
||||
{
|
||||
actionModel.AttributeRouteModel = new AttributeRouteModel(routeTemplateProvider);
|
||||
}
|
||||
|
||||
return actionModel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ParameterModel"/> for the given <see cref="ParameterInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="parameterInfo">The <see cref="ParameterInfo"/>.</param>
|
||||
/// <returns>A <see cref="ParameterModel"/> for the given <see cref="ParameterInfo"/>.</returns>
|
||||
protected virtual ParameterModel CreateParameterModel([NotNull] ParameterInfo parameterInfo)
|
||||
{
|
||||
var parameterModel = new ParameterModel(parameterInfo);
|
||||
|
||||
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
|
||||
// is needed to so that the result of ToArray() is object
|
||||
var attributes = parameterInfo.GetCustomAttributes(inherit: true).OfType<object>().ToArray();
|
||||
parameterModel.Attributes.AddRange(attributes);
|
||||
|
||||
parameterModel.BinderMetadata = attributes.OfType<IBinderMetadata>().FirstOrDefault();
|
||||
|
||||
parameterModel.ParameterName = parameterInfo.Name;
|
||||
parameterModel.IsOptional = parameterInfo.HasDefaultValue;
|
||||
|
||||
return parameterModel;
|
||||
}
|
||||
|
||||
private bool IsSilentRouteAttribute(IRouteTemplateProvider routeTemplateProvider)
|
||||
{
|
||||
return
|
||||
routeTemplateProvider.Template == null &&
|
||||
routeTemplateProvider.Order == null &&
|
||||
routeTemplateProvider.Name == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ApplicationModel
|
||||
{
|
||||
/// <summary>
|
||||
/// A default implementation of <see cref="IControllerModelBuilder"/>.
|
||||
/// </summary>
|
||||
public class DefaultControllerModelBuilder : IControllerModelBuilder
|
||||
{
|
||||
private readonly IActionModelBuilder _actionModelBuilder;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DefaultControllerModelBuilder"/>.
|
||||
/// </summary>
|
||||
/// <param name="actionModelBuilder">The <see cref="IActionModelBuilder"/> used to create actions.</param>
|
||||
public DefaultControllerModelBuilder(IActionModelBuilder actionModelBuilder)
|
||||
{
|
||||
_actionModelBuilder = actionModelBuilder;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ControllerModel BuildControllerModel([NotNull] TypeInfo typeInfo)
|
||||
{
|
||||
if (!IsController(typeInfo))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var controllerModel = CreateControllerModel(typeInfo);
|
||||
|
||||
foreach (var methodInfo in typeInfo.AsType().GetMethods())
|
||||
{
|
||||
var actionModels = _actionModelBuilder.BuildActionModels(methodInfo);
|
||||
if (actionModels != null)
|
||||
{
|
||||
foreach (var actionModel in actionModels)
|
||||
{
|
||||
actionModel.Controller = controllerModel;
|
||||
controllerModel.Actions.Add(actionModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controllerModel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if the <paramref name="typeInfo"/> is a controller. Otherwise <c>false</c>.
|
||||
/// </summary>
|
||||
/// <param name="typeInfo">The <see cref="TypeInfo"/>.</param>
|
||||
/// <returns><c>true</c> if the <paramref name="typeInfo"/> is a controller. Otherwise <c>false</c>.</returns>
|
||||
/// <remarks>
|
||||
/// Override this method to provide custom logic to determine which types are considered controllers.
|
||||
/// </remarks>
|
||||
protected virtual bool IsController([NotNull] TypeInfo typeInfo)
|
||||
{
|
||||
if (!typeInfo.IsClass ||
|
||||
typeInfo.IsAbstract ||
|
||||
|
||||
// We only consider public top-level classes as controllers. IsPublic returns false for nested
|
||||
// classes, regardless of visibility modifiers.
|
||||
!typeInfo.IsPublic ||
|
||||
typeInfo.ContainsGenericParameters)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeInfo.Name.Equals("Controller", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ||
|
||||
typeof(Controller).GetTypeInfo().IsAssignableFrom(typeInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="ControllerModel"/> for the given <see cref="TypeInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="typeInfo">The <see cref="TypeInfo"/>.</param>
|
||||
/// <returns>A <see cref="ControllerModel"/> for the given <see cref="TypeInfo"/>.</returns>
|
||||
protected virtual ControllerModel CreateControllerModel([NotNull] TypeInfo typeInfo)
|
||||
{
|
||||
var controllerModel = new ControllerModel(typeInfo);
|
||||
|
||||
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
|
||||
// is needed to so that the result of ToArray() is object
|
||||
var attributes = typeInfo.GetCustomAttributes(inherit: true).OfType<object>().ToArray();
|
||||
controllerModel.Attributes.AddRange(attributes);
|
||||
|
||||
controllerModel.ControllerName =
|
||||
typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ?
|
||||
typeInfo.Name.Substring(0, typeInfo.Name.Length - "Controller".Length) :
|
||||
typeInfo.Name;
|
||||
|
||||
controllerModel.ActionConstraints.AddRange(attributes.OfType<IActionConstraintMetadata>());
|
||||
controllerModel.Filters.AddRange(attributes.OfType<IFilter>());
|
||||
controllerModel.RouteConstraints.AddRange(attributes.OfType<RouteConstraintAttribute>());
|
||||
|
||||
controllerModel.AttributeRoutes.AddRange(
|
||||
attributes.OfType<IRouteTemplateProvider>().Select(rtp => new AttributeRouteModel(rtp)));
|
||||
|
||||
var apiVisibility = attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault();
|
||||
if (apiVisibility != null)
|
||||
{
|
||||
controllerModel.ApiExplorerIsVisible = !apiVisibility.IgnoreApi;
|
||||
}
|
||||
|
||||
var apiGroupName = attributes.OfType<IApiDescriptionGroupNameProvider>().FirstOrDefault();
|
||||
if (apiGroupName != null)
|
||||
{
|
||||
controllerModel.ApiExplorerGroupName = apiGroupName.GroupName;
|
||||
}
|
||||
|
||||
return controllerModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ApplicationModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a set of <see cref="ActionModel"/> for a method.
|
||||
/// </summary>
|
||||
public interface IActionModelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a set of <see cref="ActionModel"/> for a method. May return null or empty if
|
||||
/// <paramref name="methodInfo"/> is not an action method.
|
||||
/// </summary>
|
||||
/// <param name="methodInfo">The <see cref="MethodInfo"/>.</param>
|
||||
/// <returns>A set of <see cref="ActionModel"/> or null.</returns>
|
||||
/// <remarks>
|
||||
/// Instances of <see cref="ActionModel"/> returned from this interface should have their
|
||||
/// <see cref="ActionModel.Parameters"/> initialized.
|
||||
/// </remarks>
|
||||
IEnumerable<ActionModel> BuildActionModels([NotNull] MethodInfo methodInfo);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ApplicationModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a set of <see cref="ControllerModel"/> for a type.
|
||||
/// </summary>
|
||||
public interface IControllerModelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a set of <see cref="ControllerModel"/> for a type. May return null or empty if
|
||||
/// <paramref name="typeInfo"/> is not a controller type.
|
||||
/// </summary>
|
||||
/// <param name="typeInfo">The <see cref="TypeInfo"/>.</param>
|
||||
/// <returns>A <see cref="ControllerModel"/> or null.</returns>
|
||||
/// <remarks>
|
||||
/// Instances of <see cref="ControllerModel"/> returned from this interface should have their
|
||||
/// <see cref="ControllerModel.Actions"/> initialized.
|
||||
/// </remarks>
|
||||
ControllerModel BuildControllerModel([NotNull] TypeInfo typeInfo);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,830 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.ApplicationModel;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates instances of <see cref="ControllerActionDescriptor"/> from <see cref="GlobalModel"/>.
|
||||
/// </summary>
|
||||
public static class ControllerActionDescriptorBuilder
|
||||
{
|
||||
// This is the default order for attribute routes whose order calculated from
|
||||
// the controller model is null.
|
||||
private const int DefaultAttributeRouteOrder = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates instances of <see cref="ControllerActionDescriptor"/> from <see cref="GlobalModel"/>.
|
||||
/// </summary>
|
||||
/// <param name="application">The <see cref="GlobalModel"/>.</param>
|
||||
/// <returns>The list of <see cref="ControllerActionDescriptor"/>.</returns>
|
||||
public static IList<ControllerActionDescriptor> Build(GlobalModel application)
|
||||
{
|
||||
var actions = new List<ControllerActionDescriptor>();
|
||||
|
||||
var hasAttributeRoutes = false;
|
||||
var removalConstraints = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var methodInfoMap = new MethodToActionMap();
|
||||
|
||||
var routeTemplateErrors = new List<string>();
|
||||
var attributeRoutingConfigurationErrors = new Dictionary<MethodInfo, string>();
|
||||
|
||||
foreach (var controller in application.Controllers)
|
||||
{
|
||||
var controllerDescriptor = new ControllerDescriptor()
|
||||
{
|
||||
ControllerTypeInfo = controller.ControllerType,
|
||||
Name = controller.ControllerName,
|
||||
};
|
||||
|
||||
foreach (var action in controller.Actions)
|
||||
{
|
||||
// Controllers with multiple [Route] attributes (or user defined implementation of
|
||||
// IRouteTemplateProvider) will generate one action descriptor per IRouteTemplateProvider
|
||||
// instance.
|
||||
// Actions with multiple [Http*] attributes or other (IRouteTemplateProvider implementations
|
||||
// have already been identified as different actions during action discovery.
|
||||
var actionDescriptors = CreateActionDescriptors(application, controller, action);
|
||||
|
||||
foreach (var actionDescriptor in actionDescriptors)
|
||||
{
|
||||
actionDescriptor.ControllerDescriptor = controllerDescriptor;
|
||||
|
||||
AddApiExplorerInfo(actionDescriptor, action, controller);
|
||||
AddRouteConstraints(actionDescriptor, controller, action);
|
||||
AddControllerRouteConstraints(
|
||||
actionDescriptor,
|
||||
controller.RouteConstraints,
|
||||
removalConstraints);
|
||||
|
||||
if (IsAttributeRoutedAction(actionDescriptor))
|
||||
{
|
||||
hasAttributeRoutes = true;
|
||||
|
||||
// An attribute routed action will ignore conventional routed constraints. We still
|
||||
// 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.
|
||||
ReplaceAttributeRouteTokens(actionDescriptor, routeTemplateErrors);
|
||||
|
||||
// Attribute routed actions will ignore conventional routed constraints. Instead they have
|
||||
// a single route constraint "RouteGroup" associated with it.
|
||||
ReplaceRouteConstraints(actionDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
methodInfoMap.AddToMethodInfo(action, actionDescriptors);
|
||||
actions.AddRange(actionDescriptors);
|
||||
}
|
||||
}
|
||||
|
||||
var actionsByRouteName = new Dictionary<string, IList<ActionDescriptor>>(
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Keeps track of all the methods that we've validated to avoid visiting each action group
|
||||
// more than once.
|
||||
var validatedMethods = new HashSet<MethodInfo>();
|
||||
|
||||
foreach (var actionDescriptor in actions)
|
||||
{
|
||||
if (!validatedMethods.Contains(actionDescriptor.MethodInfo))
|
||||
{
|
||||
ValidateActionGroupConfiguration(
|
||||
methodInfoMap,
|
||||
actionDescriptor,
|
||||
attributeRoutingConfigurationErrors);
|
||||
|
||||
validatedMethods.Add(actionDescriptor.MethodInfo);
|
||||
}
|
||||
|
||||
if (!IsAttributeRoutedAction(actionDescriptor))
|
||||
{
|
||||
// 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(
|
||||
AttributeRouting.RouteGroupKey,
|
||||
string.Empty));
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
var attributeRouteInfo = actionDescriptor.AttributeRouteInfo;
|
||||
if (attributeRouteInfo.Name != null)
|
||||
{
|
||||
// Build a map of attribute route name to action descriptors to ensure that all
|
||||
// attribute routes with a given name have the same template.
|
||||
AddActionToNamedGroup(actionsByRouteName, attributeRouteInfo.Name, actionDescriptor);
|
||||
}
|
||||
|
||||
// We still want to add a 'null' for any constraint with DenyKey so that link generation
|
||||
// works properly.
|
||||
//
|
||||
// Consider an action like { area = "", controller = "Home", action = "Index" }. Even if
|
||||
// it's attribute routed, it needs to know that area must be null to generate a link.
|
||||
foreach (var key in removalConstraints)
|
||||
{
|
||||
if (!actionDescriptor.RouteValueDefaults.ContainsKey(key))
|
||||
{
|
||||
actionDescriptor.RouteValueDefaults.Add(key, value: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attributeRoutingConfigurationErrors.Any())
|
||||
{
|
||||
var message = CreateAttributeRoutingAggregateErrorMessage(
|
||||
attributeRoutingConfigurationErrors.Values);
|
||||
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
var namedRoutedErrors = ValidateNamedAttributeRoutedActions(actionsByRouteName);
|
||||
if (namedRoutedErrors.Any())
|
||||
{
|
||||
var message = CreateAttributeRoutingAggregateErrorMessage(namedRoutedErrors);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
if (routeTemplateErrors.Any())
|
||||
{
|
||||
var message = CreateAttributeRoutingAggregateErrorMessage(routeTemplateErrors);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private static IList<ControllerActionDescriptor> CreateActionDescriptors(
|
||||
GlobalModel application,
|
||||
ControllerModel controller,
|
||||
ActionModel action)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// We're overriding the attribute routes on the controller, so filter out any metadata
|
||||
// from controller level routes.
|
||||
var actionDescriptor = CreateActionDescriptor(
|
||||
action,
|
||||
controllerAttributeRoute: null);
|
||||
|
||||
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)
|
||||
{
|
||||
var actionDescriptor = CreateActionDescriptor(
|
||||
action,
|
||||
controllerAttributeRoute);
|
||||
|
||||
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 == controllerAttributeRoute?.Attribute || !(c is IRouteTemplateProvider));
|
||||
AddActionFilters(actionDescriptor, action.Filters, controllerFilters, application.Filters);
|
||||
|
||||
var controllerConstraints = controller.ActionConstraints
|
||||
.Where(c => c == controllerAttributeRoute?.Attribute || !(c is IRouteTemplateProvider));
|
||||
AddActionConstraints(actionDescriptor, action, controllerConstraints);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No attribute routes on the controller
|
||||
var actionDescriptor = CreateActionDescriptor(
|
||||
action,
|
||||
controllerAttributeRoute: null);
|
||||
actionDescriptors.Add(actionDescriptor);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
return actionDescriptors;
|
||||
}
|
||||
|
||||
private static ControllerActionDescriptor CreateActionDescriptor(
|
||||
ActionModel action,
|
||||
AttributeRouteModel controllerAttributeRoute)
|
||||
{
|
||||
var parameterDescriptors = new List<ParameterDescriptor>();
|
||||
foreach (var parameter in action.Parameters)
|
||||
{
|
||||
var parameterDescriptor = CreateParameterDescriptor(parameter);
|
||||
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,
|
||||
};
|
||||
|
||||
actionDescriptor.DisplayName = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}.{1}",
|
||||
action.ActionMethod.DeclaringType.FullName,
|
||||
action.ActionMethod.Name);
|
||||
|
||||
return actionDescriptor;
|
||||
}
|
||||
|
||||
private static ParameterDescriptor CreateParameterDescriptor(ParameterModel parameter)
|
||||
{
|
||||
var parameterDescriptor = new ParameterDescriptor()
|
||||
{
|
||||
BinderMetadata = parameter.BinderMetadata,
|
||||
IsOptional = parameter.IsOptional,
|
||||
Name = parameter.ParameterName,
|
||||
ParameterType = parameter.ParameterInfo.ParameterType,
|
||||
};
|
||||
|
||||
return parameterDescriptor;
|
||||
}
|
||||
|
||||
private static void AddApiExplorerInfo(
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
ActionModel action,
|
||||
ControllerModel controller)
|
||||
{
|
||||
var apiExplorerIsVisible = action.ApiExplorerIsVisible ?? controller.ApiExplorerIsVisible ?? false;
|
||||
if (apiExplorerIsVisible)
|
||||
{
|
||||
var apiExplorerActionData = new ApiDescriptionActionData()
|
||||
{
|
||||
GroupName = action.ApiExplorerGroupName ?? controller.ApiExplorerGroupName,
|
||||
};
|
||||
|
||||
actionDescriptor.SetProperty(apiExplorerActionData);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddActionFilters(
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
IEnumerable<IFilter> actionFilters,
|
||||
IEnumerable<IFilter> controllerFilters,
|
||||
IEnumerable<IFilter> globalFilters)
|
||||
{
|
||||
actionDescriptor.FilterDescriptors =
|
||||
actionFilters.Select(f => new FilterDescriptor(f, FilterScope.Action))
|
||||
.Concat(controllerFilters.Select(f => new FilterDescriptor(f, FilterScope.Controller)))
|
||||
.Concat(globalFilters.Select(f => new FilterDescriptor(f, FilterScope.Global)))
|
||||
.OrderBy(d => d, FilterDescriptorOrderComparer.Comparer)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static AttributeRouteInfo CreateAttributeRouteInfo(
|
||||
AttributeRouteModel action,
|
||||
AttributeRouteModel controller)
|
||||
{
|
||||
var combinedRoute = AttributeRouteModel.CombineAttributeRouteModel(
|
||||
controller,
|
||||
action);
|
||||
|
||||
if (combinedRoute == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new AttributeRouteInfo()
|
||||
{
|
||||
Template = combinedRoute.Template,
|
||||
Order = combinedRoute.Order ?? DefaultAttributeRouteOrder,
|
||||
Name = combinedRoute.Name,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddActionConstraints(
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
ActionModel action,
|
||||
IEnumerable<IActionConstraintMetadata> controllerConstraints)
|
||||
{
|
||||
var constraints = new List<IActionConstraintMetadata>();
|
||||
|
||||
var httpMethods = action.HttpMethods;
|
||||
if (httpMethods != null && httpMethods.Count > 0)
|
||||
{
|
||||
constraints.Add(new HttpMethodConstraint(httpMethods));
|
||||
}
|
||||
|
||||
if (action.ActionConstraints != null)
|
||||
{
|
||||
constraints.AddRange(action.ActionConstraints);
|
||||
}
|
||||
|
||||
if (controllerConstraints != null)
|
||||
{
|
||||
constraints.AddRange(controllerConstraints);
|
||||
}
|
||||
|
||||
if (constraints.Count > 0)
|
||||
{
|
||||
actionDescriptor.ActionConstraints = constraints;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddRouteConstraints(
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
ControllerModel controller,
|
||||
ActionModel action)
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
"controller",
|
||||
controller.ControllerName));
|
||||
|
||||
if (action.IsActionNameMatchRequired)
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
"action",
|
||||
action.ActionName));
|
||||
}
|
||||
else
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
"action",
|
||||
string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddControllerRouteConstraints(
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
IList<RouteConstraintAttribute> routeconstraints,
|
||||
ISet<string> removalConstraints)
|
||||
{
|
||||
// Apply all the constraints defined on the controller (for example, [Area]) to the actions
|
||||
// in that controller. Also keep track of all the constraints that require preventing actions
|
||||
// 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.RouteKeyHandling == RouteKeyHandling.CatchAll)
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(
|
||||
RouteDataActionConstraint.CreateCatchAll(
|
||||
constraintAttribute.RouteKey));
|
||||
}
|
||||
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(ControllerActionDescriptor 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(
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
IList<string> routeTemplateErrors)
|
||||
{
|
||||
try
|
||||
{
|
||||
actionDescriptor.AttributeRouteInfo.Template = AttributeRouteModel.ReplaceTokens(
|
||||
actionDescriptor.AttributeRouteInfo.Template,
|
||||
actionDescriptor.RouteValueDefaults);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
// Routing will throw an InvalidOperationException here if we can't parse/replace tokens
|
||||
// in the template.
|
||||
var message = Resources.FormatAttributeRoute_IndividualErrorMessage(
|
||||
actionDescriptor.DisplayName,
|
||||
Environment.NewLine,
|
||||
ex.Message);
|
||||
|
||||
routeTemplateErrors.Add(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddConstraintsAsDefaultRouteValues(ControllerActionDescriptor 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(
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
ISet<string> removalConstraints)
|
||||
{
|
||||
foreach (var key in removalConstraints)
|
||||
{
|
||||
if (!HasConstraint(actionDescriptor.RouteConstraints, key))
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
key,
|
||||
string.Empty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddActionToNamedGroup(
|
||||
IDictionary<string, IList<ActionDescriptor>> actionsByRouteName,
|
||||
string routeName,
|
||||
ControllerActionDescriptor 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(ControllerActionDescriptor actionDescriptor)
|
||||
{
|
||||
return actionDescriptor.AttributeRouteInfo?.Template != null;
|
||||
}
|
||||
|
||||
private static IList<string> AddErrorNumbers(
|
||||
IEnumerable<string> namedRoutedErrors)
|
||||
{
|
||||
return namedRoutedErrors
|
||||
.Select((error, i) =>
|
||||
Resources.FormatAttributeRoute_AggregateErrorMessage_ErrorNumber(
|
||||
i + 1,
|
||||
Environment.NewLine,
|
||||
error))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IList<string> ValidateNamedAttributeRoutedActions(
|
||||
IDictionary<string,
|
||||
IList<ActionDescriptor>> actionsGroupedByRouteName)
|
||||
{
|
||||
var namedRouteErrors = new List<string>();
|
||||
|
||||
foreach (var kvp in actionsGroupedByRouteName)
|
||||
{
|
||||
// We are looking for attribute routed actions that have the same name but
|
||||
// different route templates. We pick the first template of the group and
|
||||
// we compare it against the rest of the templates that have that same name
|
||||
// associated.
|
||||
// The moment we find one that is different we report the whole group to the
|
||||
// user in the error message so that he can see the different actions and the
|
||||
// different templates for a given named attribute route.
|
||||
var firstActionDescriptor = kvp.Value[0];
|
||||
var firstTemplate = firstActionDescriptor.AttributeRouteInfo.Template;
|
||||
|
||||
for (var i = 1; i < kvp.Value.Count; i++)
|
||||
{
|
||||
var otherActionDescriptor = kvp.Value[i];
|
||||
var otherActionTemplate = otherActionDescriptor.AttributeRouteInfo.Template;
|
||||
|
||||
if (!firstTemplate.Equals(otherActionTemplate, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var descriptions = kvp.Value.Select(ad =>
|
||||
Resources.FormatAttributeRoute_DuplicateNames_Item(
|
||||
ad.DisplayName,
|
||||
ad.AttributeRouteInfo.Template));
|
||||
|
||||
var errorDescription = string.Join(Environment.NewLine, descriptions);
|
||||
var message = Resources.FormatAttributeRoute_DuplicateNames(
|
||||
kvp.Key,
|
||||
Environment.NewLine,
|
||||
errorDescription);
|
||||
|
||||
namedRouteErrors.Add(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return namedRouteErrors;
|
||||
}
|
||||
|
||||
private static void ValidateActionGroupConfiguration(
|
||||
IDictionary<MethodInfo, IDictionary<ActionModel, IList<ControllerActionDescriptor>>> methodMap,
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
IDictionary<MethodInfo, string> routingConfigurationErrors)
|
||||
{
|
||||
string combinedErrorMessage = null;
|
||||
|
||||
var hasAttributeRoutedActions = false;
|
||||
var hasConventionallyRoutedActions = false;
|
||||
|
||||
var invalidHttpMethodActions = new Dictionary<ActionModel, IEnumerable<string>>();
|
||||
|
||||
var actionsForMethod = methodMap[actionDescriptor.MethodInfo];
|
||||
foreach (var reflectedAction in actionsForMethod)
|
||||
{
|
||||
foreach (var action in reflectedAction.Value)
|
||||
{
|
||||
if (IsAttributeRoutedAction(action))
|
||||
{
|
||||
hasAttributeRoutedActions = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasConventionallyRoutedActions = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep a list of actions with possible invalid IHttpActionMethodProvider attributes
|
||||
// to generate an error in case the method generates attribute routed actions.
|
||||
ValidateActionHttpMethodProviders(reflectedAction.Key, invalidHttpMethodActions);
|
||||
}
|
||||
|
||||
// Validate that no method result in attribute and non attribute actions at the same time.
|
||||
// By design, mixing attribute and conventionally actions in the same method is not allowed.
|
||||
// This is for example the case when someone uses[HttpGet("Products")] and[HttpPost]
|
||||
// on the same method.
|
||||
if (hasAttributeRoutedActions && hasConventionallyRoutedActions)
|
||||
{
|
||||
combinedErrorMessage = CreateMixedRoutedActionDescriptorsErrorMessage(
|
||||
actionDescriptor,
|
||||
actionsForMethod);
|
||||
}
|
||||
|
||||
// Validate that no method that creates attribute routed actions and
|
||||
// also uses attributes that only constrain the set of HTTP methods. For example,
|
||||
// if an attribute that implements IActionHttpMethodProvider but does not implement
|
||||
// IRouteTemplateProvider is used with an attribute that implements IRouteTemplateProvider on
|
||||
// the same action, the HTTP methods provided by the attribute that only implements
|
||||
// IActionHttpMethodProvider would be silently ignored, so we choose to throw to
|
||||
// inform the user of the invalid configuration.
|
||||
if (hasAttributeRoutedActions && invalidHttpMethodActions.Any())
|
||||
{
|
||||
var errorMessage = CreateInvalidActionHttpMethodProviderErrorMessage(
|
||||
actionDescriptor,
|
||||
invalidHttpMethodActions,
|
||||
actionsForMethod);
|
||||
|
||||
combinedErrorMessage = CombineErrorMessage(combinedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
if (combinedErrorMessage != null)
|
||||
{
|
||||
routingConfigurationErrors.Add(actionDescriptor.MethodInfo, combinedErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateActionHttpMethodProviders(
|
||||
ActionModel reflectedAction,
|
||||
IDictionary<ActionModel, IEnumerable<string>> invalidHttpMethodActions)
|
||||
{
|
||||
var invalidHttpMethodProviderAttributes = reflectedAction.Attributes
|
||||
.Where(attr => attr is IActionHttpMethodProvider &&
|
||||
!(attr is IRouteTemplateProvider))
|
||||
.Select(attr => attr.GetType().FullName);
|
||||
|
||||
if (invalidHttpMethodProviderAttributes.Any())
|
||||
{
|
||||
invalidHttpMethodActions.Add(
|
||||
reflectedAction,
|
||||
invalidHttpMethodProviderAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
private static string CombineErrorMessage(string combinedErrorMessage, string errorMessage)
|
||||
{
|
||||
if (combinedErrorMessage == null)
|
||||
{
|
||||
combinedErrorMessage = errorMessage;
|
||||
}
|
||||
else
|
||||
{
|
||||
combinedErrorMessage = string.Join(
|
||||
Environment.NewLine,
|
||||
combinedErrorMessage,
|
||||
errorMessage);
|
||||
}
|
||||
|
||||
return combinedErrorMessage;
|
||||
}
|
||||
|
||||
private static string CreateInvalidActionHttpMethodProviderErrorMessage(
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
IDictionary<ActionModel, IEnumerable<string>> invalidHttpMethodActions,
|
||||
IDictionary<ActionModel, IList<ControllerActionDescriptor>> actionsForMethod)
|
||||
{
|
||||
var messagesForMethodInfo = new List<string>();
|
||||
foreach (var invalidAction in invalidHttpMethodActions)
|
||||
{
|
||||
var invalidAttributesList = string.Join(", ", invalidAction.Value);
|
||||
|
||||
foreach (var descriptor in actionsForMethod[invalidAction.Key])
|
||||
{
|
||||
// We only report errors in attribute routed actions. For example, an action
|
||||
// that contains [HttpGet("Products")], [HttpPost] and [HttpHead], where [HttpHead]
|
||||
// only implements IHttpActionMethodProvider and restricts the action to only allow
|
||||
// the head method, will report that the action contains invalid IActionHttpMethodProvider
|
||||
// attributes only for the action generated by [HttpGet("Products")].
|
||||
// [HttpPost] will be treated as an action that produces a conventionally routed action
|
||||
// and the fact that the method generates attribute and non attributed actions will be
|
||||
// reported as a different error.
|
||||
if (IsAttributeRoutedAction(descriptor))
|
||||
{
|
||||
var messageItem = Resources.FormatAttributeRoute_InvalidHttpConstraints_Item(
|
||||
descriptor.DisplayName,
|
||||
descriptor.AttributeRouteInfo.Template,
|
||||
invalidAttributesList,
|
||||
typeof(IActionHttpMethodProvider).FullName);
|
||||
|
||||
messagesForMethodInfo.Add(messageItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var methodFullName = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}.{1}",
|
||||
actionDescriptor.MethodInfo.DeclaringType.FullName,
|
||||
actionDescriptor.MethodInfo.Name);
|
||||
|
||||
// Sample message:
|
||||
// A method 'MyApplication.CustomerController.Index' that defines attribute routed actions must
|
||||
// not have attributes that implement 'Microsoft.AspNet.Mvc.IActionHttpMethodProvider'
|
||||
// and do not implement 'Microsoft.AspNet.Mvc.Routing.IRouteTemplateProvider':
|
||||
// Action 'MyApplication.CustomerController.Index' has 'Namespace.CustomHttpMethodAttribute'
|
||||
// invalid 'Microsoft.AspNet.Mvc.IActionHttpMethodProvider' attributes.
|
||||
return
|
||||
Resources.FormatAttributeRoute_InvalidHttpConstraints(
|
||||
methodFullName,
|
||||
typeof(IActionHttpMethodProvider).FullName,
|
||||
typeof(IRouteTemplateProvider).FullName,
|
||||
Environment.NewLine,
|
||||
string.Join(Environment.NewLine, messagesForMethodInfo));
|
||||
}
|
||||
|
||||
private static string CreateMixedRoutedActionDescriptorsErrorMessage(
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
IDictionary<ActionModel, IList<ControllerActionDescriptor>> actionsForMethod)
|
||||
{
|
||||
// Text to show as the attribute route template for conventionally routed actions.
|
||||
var nullTemplate = Resources.AttributeRoute_NullTemplateRepresentation;
|
||||
|
||||
var actionDescriptions = actionsForMethod
|
||||
.SelectMany(a => a.Value)
|
||||
.Select(ad =>
|
||||
Resources.FormatAttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod_Item(
|
||||
ad.DisplayName,
|
||||
ad.AttributeRouteInfo != null ? ad.AttributeRouteInfo.Template : nullTemplate));
|
||||
|
||||
var methodFullName = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}.{1}",
|
||||
actionDescriptor.MethodInfo.DeclaringType.FullName,
|
||||
actionDescriptor.MethodInfo.Name);
|
||||
|
||||
// Sample error message:
|
||||
// A method 'MyApplication.CustomerController.Index' must not define attributed actions and
|
||||
// non attributed actions at the same time:
|
||||
// Action: 'MyApplication.CustomerController.Index' - Template: 'Products'
|
||||
// Action: 'MyApplication.CustomerController.Index' - Template: '(none)'
|
||||
return
|
||||
Resources.FormatAttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod(
|
||||
methodFullName,
|
||||
Environment.NewLine,
|
||||
string.Join(Environment.NewLine, actionDescriptions));
|
||||
}
|
||||
|
||||
private static string CreateAttributeRoutingAggregateErrorMessage(
|
||||
IEnumerable<string> individualErrors)
|
||||
{
|
||||
var errorMessages = AddErrorNumbers(individualErrors);
|
||||
|
||||
var message = Resources.FormatAttributeRoute_AggregateErrorMessage(
|
||||
Environment.NewLine,
|
||||
string.Join(Environment.NewLine + Environment.NewLine, errorMessages));
|
||||
return message;
|
||||
}
|
||||
|
||||
private static string GetRouteGroupValue(int order, string template)
|
||||
{
|
||||
var group = string.Format(CultureInfo.InvariantCulture, "{0}-{1}", order, template);
|
||||
return ("__route__" + group).ToUpperInvariant();
|
||||
}
|
||||
|
||||
// We need to build a map of methods to reflected actions and reflected actions to
|
||||
// action descriptors so that we can validate later that no method produced attribute
|
||||
// and non attributed actions at the same time, and that no method that produced attribute
|
||||
// routed actions has no attributes that implement IActionHttpMethodProvider and do not
|
||||
// implement IRouteTemplateProvider. For example:
|
||||
//
|
||||
// public class ProductsController
|
||||
// {
|
||||
// [HttpGet("Products")]
|
||||
// [HttpPost]
|
||||
// public ActionResult Items(){ ... }
|
||||
//
|
||||
// [HttpGet("Products")]
|
||||
// [CustomHttpMethods("POST, PUT")]
|
||||
// public ActionResult List(){ ... }
|
||||
// }
|
||||
private class MethodToActionMap :
|
||||
Dictionary<MethodInfo, IDictionary<ActionModel, IList<ControllerActionDescriptor>>>
|
||||
{
|
||||
public void AddToMethodInfo(ActionModel action,
|
||||
IList<ControllerActionDescriptor> actionDescriptors)
|
||||
{
|
||||
IDictionary<ActionModel, IList<ControllerActionDescriptor>> actionsForMethod = null;
|
||||
if (TryGetValue(action.ActionMethod, out actionsForMethod))
|
||||
{
|
||||
actionsForMethod.Add(action, actionDescriptors);
|
||||
}
|
||||
else
|
||||
{
|
||||
var reflectedActionMap =
|
||||
new Dictionary<ActionModel, IList<ControllerActionDescriptor>>();
|
||||
reflectedActionMap.Add(action, actionDescriptors);
|
||||
Add(action.ActionMethod, reflectedActionMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,266 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class DefaultActionDiscoveryConventions : IActionDiscoveryConventions
|
||||
{
|
||||
public virtual bool IsController([NotNull] TypeInfo typeInfo)
|
||||
{
|
||||
if (!typeInfo.IsClass ||
|
||||
typeInfo.IsAbstract ||
|
||||
|
||||
// We only consider public top-level classes as controllers. IsPublic returns false for nested
|
||||
// classes, regardless of visibility modifiers.
|
||||
!typeInfo.IsPublic ||
|
||||
typeInfo.ContainsGenericParameters)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeInfo.Name.Equals("Controller", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ||
|
||||
typeof(Controller).GetTypeInfo().IsAssignableFrom(typeInfo);
|
||||
}
|
||||
|
||||
// If the convention is All methods starting with Get do not have an action name,
|
||||
// for a input GetXYZ methodInfo, the return value will be
|
||||
// { { HttpMethods = "GET", ActionName = "GetXYZ", RequireActionNameMatch = false, AttributeRoute = null }}
|
||||
public virtual IEnumerable<ActionInfo> GetActions(
|
||||
[NotNull] MethodInfo methodInfo,
|
||||
[NotNull] TypeInfo controllerTypeInfo)
|
||||
{
|
||||
if (!IsValidActionMethod(methodInfo))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var attributes = GetActionCustomAttributes(methodInfo);
|
||||
var actionInfos = GetActionsForMethodsWithCustomAttributes(attributes, methodInfo, controllerTypeInfo);
|
||||
if (actionInfos.Any())
|
||||
{
|
||||
return actionInfos;
|
||||
}
|
||||
else
|
||||
{
|
||||
// By default the action is just matched by name.
|
||||
actionInfos = new ActionInfo[]
|
||||
{
|
||||
new ActionInfo()
|
||||
{
|
||||
ActionName = methodInfo.Name,
|
||||
Attributes = attributes.Attributes,
|
||||
RequireActionNameMatch = true,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return actionInfos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the method is a valid action.
|
||||
/// </summary>
|
||||
/// <param name="method">The <see cref="MethodInfo"/>.</param>
|
||||
/// <returns>true if the method is a valid action. Otherwise, false.</returns>
|
||||
public virtual bool IsValidActionMethod(MethodInfo method)
|
||||
{
|
||||
return
|
||||
method.IsPublic &&
|
||||
!method.IsStatic &&
|
||||
!method.IsAbstract &&
|
||||
!method.IsConstructor &&
|
||||
!method.IsGenericMethod &&
|
||||
|
||||
// The SpecialName bit is set to flag members that are treated in a special way by some compilers
|
||||
// (such as property accessors and operator overloading methods).
|
||||
!method.IsSpecialName &&
|
||||
!method.IsDefined(typeof(NonActionAttribute)) &&
|
||||
|
||||
// Overriden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid.
|
||||
method.GetBaseDefinition().DeclaringType != typeof(object);
|
||||
}
|
||||
|
||||
private ActionAttributes GetActionCustomAttributes(MethodInfo methodInfo)
|
||||
{
|
||||
var attributes = methodInfo.GetCustomAttributes(inherit: true).OfType<object>().ToArray();
|
||||
var actionNameAttribute = attributes.OfType<ActionNameAttribute>().FirstOrDefault();
|
||||
var httpMethodConstraints = attributes.OfType<IActionHttpMethodProvider>();
|
||||
var routeTemplates = attributes.OfType<IRouteTemplateProvider>();
|
||||
|
||||
return new ActionAttributes()
|
||||
{
|
||||
Attributes = attributes,
|
||||
ActionNameAttribute = actionNameAttribute,
|
||||
HttpMethodProviderAttributes = httpMethodConstraints,
|
||||
RouteTemplateProviderAttributes = routeTemplates,
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<ActionInfo> GetActionsForMethodsWithCustomAttributes(
|
||||
ActionAttributes actionAttributes,
|
||||
MethodInfo methodInfo,
|
||||
TypeInfo controller)
|
||||
{
|
||||
var hasControllerAttributeRoutes = HasValidControllerRouteTemplates(controller);
|
||||
|
||||
// We need to check controllerRouteTemplates to take into account the
|
||||
// case where the controller has [Route] on it and the action does not have any
|
||||
// attributes applied to it.
|
||||
if (actionAttributes.HasSpecialAttribute() || hasControllerAttributeRoutes)
|
||||
{
|
||||
var actionNameAttribute = actionAttributes.ActionNameAttribute;
|
||||
var actionName = actionNameAttribute != null ? actionNameAttribute.Name : methodInfo.Name;
|
||||
|
||||
// The moment we see a non null attribute route template in the method or
|
||||
// in the controller we consider the whole group to be attribute routed actions.
|
||||
// If a combination ends up producing a non attribute routed action we consider
|
||||
// that an error and throw at a later point in the pipeline.
|
||||
if (hasControllerAttributeRoutes || ActionHasAttributeRoutes(actionAttributes))
|
||||
{
|
||||
return GetAttributeRoutedActions(actionAttributes, actionName);
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetHttpConstrainedActions(actionAttributes, actionName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the action is not decorated with any of the attributes,
|
||||
// it would be handled by convention.
|
||||
return Enumerable.Empty<ActionInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ActionHasAttributeRoutes(ActionAttributes actionAttributes)
|
||||
{
|
||||
// We neet to check for null as some attributes implement IActionHttpMethodProvider
|
||||
// and IRouteTemplateProvider and allow the user to provide a null template. An example
|
||||
// of this is HttpGetAttribute. If the user provides a template, the attribute marks the
|
||||
// action as attribute routed, but in other case, the attribute only adds a constraint
|
||||
// that allows the action to be called with the GET HTTP method.
|
||||
return actionAttributes.RouteTemplateProviderAttributes
|
||||
.Any(rtp => rtp.Template != null);
|
||||
}
|
||||
|
||||
private static bool HasValidControllerRouteTemplates(TypeInfo controller)
|
||||
{
|
||||
// A method inside a controller is considered to create attribute routed actions if the controller
|
||||
// has one or more attributes that implement IRouteTemplateProvider with a non null template applied
|
||||
// to it.
|
||||
return controller.GetCustomAttributes()
|
||||
.OfType<IRouteTemplateProvider>()
|
||||
.Any(cr => cr.Template != null);
|
||||
}
|
||||
|
||||
private static IEnumerable<ActionInfo> GetHttpConstrainedActions(
|
||||
ActionAttributes actionAttributes,
|
||||
string actionName)
|
||||
{
|
||||
var httpMethodProviders = actionAttributes.HttpMethodProviderAttributes;
|
||||
var httpMethods = httpMethodProviders.SelectMany(x => x.HttpMethods).Distinct().ToArray();
|
||||
if (httpMethods.Length > 0)
|
||||
{
|
||||
foreach (var httpMethod in httpMethods)
|
||||
{
|
||||
yield return new ActionInfo()
|
||||
{
|
||||
HttpMethods = new string[] { httpMethod },
|
||||
ActionName = actionName,
|
||||
Attributes = actionAttributes.Attributes,
|
||||
RequireActionNameMatch = true,
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new ActionInfo()
|
||||
{
|
||||
HttpMethods = httpMethods,
|
||||
ActionName = actionName,
|
||||
Attributes = actionAttributes.Attributes,
|
||||
RequireActionNameMatch = true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<ActionInfo> GetAttributeRoutedActions(
|
||||
ActionAttributes actionAttributes,
|
||||
string actionName)
|
||||
{
|
||||
var actions = new List<ActionInfo>();
|
||||
|
||||
// This is the case where the controller has [Route] applied to it and
|
||||
// the action doesn't have any [Route] or [Http*] attribute applied.
|
||||
if (!actionAttributes.RouteTemplateProviderAttributes.Any())
|
||||
{
|
||||
actions.Add(new ActionInfo
|
||||
{
|
||||
Attributes = actionAttributes.Attributes,
|
||||
ActionName = actionName,
|
||||
HttpMethods = null,
|
||||
RequireActionNameMatch = true,
|
||||
AttributeRoute = null
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var routeTemplateProvider in actionAttributes.RouteTemplateProviderAttributes)
|
||||
{
|
||||
// We want to exclude the attributes from the other route template providers;
|
||||
var attributes = actionAttributes.Attributes
|
||||
.Where(a => a == routeTemplateProvider || !(a is IRouteTemplateProvider))
|
||||
.ToArray();
|
||||
|
||||
actions.Add(new ActionInfo()
|
||||
{
|
||||
Attributes = attributes,
|
||||
ActionName = actionName,
|
||||
HttpMethods = GetRouteTemplateHttpMethods(routeTemplateProvider),
|
||||
RequireActionNameMatch = true,
|
||||
AttributeRoute = routeTemplateProvider
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private static string[] GetRouteTemplateHttpMethods(IRouteTemplateProvider routeTemplateProvider)
|
||||
{
|
||||
var provider = routeTemplateProvider as IActionHttpMethodProvider;
|
||||
if (provider != null && provider.HttpMethods != null)
|
||||
{
|
||||
return provider.HttpMethods.ToArray();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private class ActionAttributes
|
||||
{
|
||||
public ActionNameAttribute ActionNameAttribute { get; set; }
|
||||
|
||||
public object[] Attributes { get; set; }
|
||||
|
||||
public IEnumerable<IActionHttpMethodProvider> HttpMethodProviderAttributes { get; set; }
|
||||
public IEnumerable<IRouteTemplateProvider> RouteTemplateProviderAttributes { get; set; }
|
||||
|
||||
public bool HasSpecialAttribute()
|
||||
{
|
||||
return ActionNameAttribute != null ||
|
||||
HttpMethodProviderAttributes.Any() ||
|
||||
RouteTemplateProviderAttributes.Any();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public interface IActionDiscoveryConventions
|
||||
{
|
||||
bool IsController(TypeInfo typeInfo);
|
||||
|
||||
IEnumerable<ActionInfo> GetActions(MethodInfo methodInfo, TypeInfo controllerTypeInfo);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Mvc.ApplicationModel;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.Filters;
|
||||
using Microsoft.AspNet.Mvc.Internal;
|
||||
|
|
@ -44,9 +45,17 @@ namespace Microsoft.AspNet.Mvc
|
|||
//
|
||||
// Core action discovery, filters and action execution.
|
||||
//
|
||||
yield return describe.Transient<IActionDiscoveryConventions, DefaultActionDiscoveryConventions>();
|
||||
// These are consumed only when creating action descriptors, then they can be de-allocated
|
||||
yield return describe.Transient<IControllerModelBuilder, DefaultControllerModelBuilder>();
|
||||
yield return describe.Transient<IActionModelBuilder, DefaultActionModelBuilder>();
|
||||
|
||||
// This accesses per-request services to activate the controller
|
||||
yield return describe.Transient<IControllerFactory, DefaultControllerFactory>();
|
||||
|
||||
// This has a cache, so it needs to be a singleton
|
||||
yield return describe.Singleton<IControllerActivator, DefaultControllerActivator>();
|
||||
|
||||
// This accesses per-reqest services
|
||||
yield return describe.Transient<IActionInvokerFactory, ActionInvokerFactory>();
|
||||
|
||||
// This provider needs access to the per-request services, but might be used many times for a given
|
||||
|
|
|
|||
|
|
@ -1,322 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNET50
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.DependencyInjection.NestedProviders;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class ActionAttributeTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("PUT")]
|
||||
[InlineData("POST")]
|
||||
[InlineData("DELETE")]
|
||||
[InlineData("PATCH")]
|
||||
public async Task HttpMethodAttribute_ActionWithMultipleHttpMethodAttributeViaAcceptVerbs_ORsMultipleHttpMethods(string verb)
|
||||
{
|
||||
// Arrange
|
||||
var routeContext = new RouteContext(GetHttpContext(verb));
|
||||
routeContext.RouteData.Values = new Dictionary<string, object>
|
||||
{
|
||||
{ "controller", "HttpMethodAttributeTests_RestOnly" },
|
||||
{ "action", "Patch" }
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await InvokeActionSelector(routeContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Patch", result.Name);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("PUT")]
|
||||
[InlineData("POST")]
|
||||
[InlineData("DELETE")]
|
||||
[InlineData("PATCH")]
|
||||
public async Task HttpMethodAttribute_ActionWithMultipleHttpMethodAttributes_ORsMultipleHttpMethods(string verb)
|
||||
{
|
||||
// Arrange
|
||||
var routeContext = new RouteContext(GetHttpContext(verb));
|
||||
routeContext.RouteData.Values = new Dictionary<string, object>()
|
||||
{
|
||||
{ "controller", "HttpMethodAttributeTests_RestOnly" },
|
||||
{ "action", "Put" }
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await InvokeActionSelector(routeContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Put", result.Name);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("PUT")]
|
||||
public async Task HttpMethodAttribute_ActionDecoratedWithHttpMethodAttribute_OverridesConvention(string verb)
|
||||
{
|
||||
// Arrange
|
||||
// Note no action name is passed, hence should return a null action descriptor.
|
||||
var routeContext = new RouteContext(GetHttpContext(verb));
|
||||
routeContext.RouteData.Values = new Dictionary<string, object>()
|
||||
{
|
||||
{ "controller", "HttpMethodAttributeTests_RestOnly" },
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await InvokeActionSelector(routeContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(null, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Put")]
|
||||
[InlineData("RPCMethod")]
|
||||
[InlineData("RPCMethodWithHttpGet")]
|
||||
public void NonActionAttribute_ActionNotReachable(string actionName)
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorProvider = GetActionDescriptorProvider();
|
||||
|
||||
// Act
|
||||
var result = actionDescriptorProvider.GetDescriptors()
|
||||
.Select(x => x as ControllerActionDescriptor)
|
||||
.FirstOrDefault(
|
||||
x=> x.ControllerName == "NonAction" &&
|
||||
x.Name == actionName);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("PUT")]
|
||||
[InlineData("POST")]
|
||||
[InlineData("DELETE")]
|
||||
[InlineData("PATCH")]
|
||||
public async Task ActionNameAttribute_ActionGetsExposedViaActionName_UnreachableByConvention(string verb)
|
||||
{
|
||||
// Arrange
|
||||
var routeContext = new RouteContext(GetHttpContext(verb));
|
||||
routeContext.RouteData.Values = new Dictionary<string, object>
|
||||
{
|
||||
{ "controller", "ActionName" },
|
||||
{ "action", "RPCMethodWithHttpGet" }
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await InvokeActionSelector(routeContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(null, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "CustomActionName_Verb")]
|
||||
[InlineData("PUT", "CustomActionName_Verb")]
|
||||
[InlineData("POST", "CustomActionName_Verb")]
|
||||
[InlineData("DELETE", "CustomActionName_Verb")]
|
||||
[InlineData("PATCH", "CustomActionName_Verb")]
|
||||
[InlineData("GET", "CustomActionName_DefaultMethod")]
|
||||
[InlineData("PUT", "CustomActionName_DefaultMethod")]
|
||||
[InlineData("POST", "CustomActionName_DefaultMethod")]
|
||||
[InlineData("DELETE", "CustomActionName_DefaultMethod")]
|
||||
[InlineData("PATCH", "CustomActionName_DefaultMethod")]
|
||||
[InlineData("GET", "CustomActionName_RpcMethod")]
|
||||
[InlineData("PUT", "CustomActionName_RpcMethod")]
|
||||
[InlineData("POST", "CustomActionName_RpcMethod")]
|
||||
[InlineData("DELETE", "CustomActionName_RpcMethod")]
|
||||
[InlineData("PATCH", "CustomActionName_RpcMethod")]
|
||||
public async Task ActionNameAttribute_DifferentActionName_UsesActionNameFromActionNameAttribute(string verb, string actionName)
|
||||
{
|
||||
// Arrange
|
||||
var routeContext = new RouteContext(GetHttpContext(verb));
|
||||
routeContext.RouteData.Values = new Dictionary<string, object>
|
||||
{
|
||||
{ "controller", "ActionName" },
|
||||
{ "action", actionName }
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await InvokeActionSelector(routeContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(actionName, result.Name);
|
||||
}
|
||||
|
||||
private async Task<ActionDescriptor> InvokeActionSelector(
|
||||
RouteContext context,
|
||||
IActionDiscoveryConventions actionDiscoveryConventions = null)
|
||||
{
|
||||
var actionDescriptorProvider = GetActionDescriptorProvider(actionDiscoveryConventions);
|
||||
var descriptorProvider =
|
||||
new NestedProviderManager<ActionDescriptorProviderContext>(new[] { actionDescriptorProvider });
|
||||
|
||||
var serviceContainer = new ServiceContainer();
|
||||
serviceContainer.AddService(typeof(INestedProviderManager<ActionDescriptorProviderContext>),
|
||||
descriptorProvider);
|
||||
|
||||
var actionCollectionDescriptorProvider = new DefaultActionDescriptorsCollectionProvider(serviceContainer);
|
||||
var decisionTreeProvider = new ActionSelectorDecisionTreeProvider(actionCollectionDescriptorProvider);
|
||||
|
||||
var actionConstraintProvider = new NestedProviderManager<ActionConstraintProviderContext>(
|
||||
new INestedProvider<ActionConstraintProviderContext>[]
|
||||
{
|
||||
new DefaultActionConstraintProvider(serviceContainer),
|
||||
});
|
||||
|
||||
var defaultActionSelector = new DefaultActionSelector(
|
||||
actionCollectionDescriptorProvider,
|
||||
decisionTreeProvider,
|
||||
actionConstraintProvider,
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
return await defaultActionSelector.SelectAsync(context);
|
||||
}
|
||||
|
||||
private ControllerActionDescriptorProvider GetActionDescriptorProvider(
|
||||
IActionDiscoveryConventions actionDiscoveryConventions = null)
|
||||
{
|
||||
var assemblyProvider = new StaticAssemblyProvider();
|
||||
|
||||
if (actionDiscoveryConventions == null)
|
||||
{
|
||||
var controllerTypes = typeof(ActionAttributeTests)
|
||||
.GetNestedTypes(BindingFlags.NonPublic)
|
||||
.Select(t => t.GetTypeInfo());
|
||||
|
||||
actionDiscoveryConventions = new StaticActionDiscoveryConventions(controllerTypes.ToArray());
|
||||
}
|
||||
|
||||
return new ControllerActionDescriptorProvider(
|
||||
assemblyProvider,
|
||||
actionDiscoveryConventions,
|
||||
new TestGlobalFilterProvider(),
|
||||
new MockMvcOptionsAccessor());
|
||||
}
|
||||
|
||||
private static HttpContext GetHttpContext(string httpMethod)
|
||||
{
|
||||
var request = new Mock<HttpRequest>();
|
||||
var headers = new Mock<IHeaderDictionary>();
|
||||
request.SetupGet(r => r.Headers).Returns(headers.Object);
|
||||
request.SetupGet(x => x.Method).Returns(httpMethod);
|
||||
var httpContext = new Mock<HttpContext>();
|
||||
httpContext.SetupGet(c => c.Request).Returns(request.Object);
|
||||
return httpContext.Object;
|
||||
}
|
||||
|
||||
private class CustomActionConvention : DefaultActionDiscoveryConventions
|
||||
{
|
||||
public override IEnumerable<ActionInfo> GetActions([NotNull]MethodInfo methodInfo, [NotNull]TypeInfo controllerTypeInfo)
|
||||
{
|
||||
var actions = new List<ActionInfo>(base.GetActions(methodInfo, controllerTypeInfo));
|
||||
if (methodInfo.Name == "PostSomething")
|
||||
{
|
||||
actions[0].HttpMethods = new string[] { "POST" };
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
#region Controller Classes
|
||||
|
||||
private class NonActionController
|
||||
{
|
||||
[NonAction]
|
||||
public void Put()
|
||||
{
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
public void RPCMethod()
|
||||
{
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
[HttpGet]
|
||||
public void RPCMethodWithHttpGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class HttpMethodAttributeTests_DefaultMethodValidationController
|
||||
{
|
||||
public void Index()
|
||||
{
|
||||
}
|
||||
|
||||
// Method with custom attribute.
|
||||
[HttpGet]
|
||||
public void Get()
|
||||
{ }
|
||||
|
||||
// InvalidMethod ( since its private)
|
||||
private void Post()
|
||||
{ }
|
||||
}
|
||||
|
||||
private class ActionNameController
|
||||
{
|
||||
[ActionName("CustomActionName_Verb")]
|
||||
public void Put()
|
||||
{
|
||||
}
|
||||
|
||||
[ActionName("CustomActionName_DefaultMethod")]
|
||||
public void Index()
|
||||
{
|
||||
}
|
||||
|
||||
[ActionName("CustomActionName_RpcMethod")]
|
||||
public void RPCMethodWithHttpGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class HttpMethodAttributeTests_RestOnlyController
|
||||
{
|
||||
[HttpGet]
|
||||
[HttpPut]
|
||||
[HttpPost]
|
||||
[HttpDelete]
|
||||
[HttpPatch]
|
||||
public void Put()
|
||||
{
|
||||
}
|
||||
|
||||
[AcceptVerbs("PUT", "post", "GET", "delete", "pATcH")]
|
||||
public void Patch()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class HttpMethodAttributeTests_DerivedController : HttpMethodAttributeTests_RestOnlyController
|
||||
{
|
||||
}
|
||||
|
||||
#endregion Controller Classes
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,603 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ApplicationModel
|
||||
{
|
||||
public class DefaultActionModelBuilderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("GetFromDerived", true)]
|
||||
[InlineData("NewMethod", true)] // "NewMethod" is a public method declared with keyword "new".
|
||||
[InlineData("GetFromBase", true)]
|
||||
public void IsAction_WithInheritedMethods(string methodName, bool expected)
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleActionModelBuilder();
|
||||
var method = typeof(DerivedController).GetMethod(methodName);
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = builder.IsAction(method);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAction_OverridenMethodControllerClass()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleActionModelBuilder();
|
||||
var method = typeof(BaseController).GetMethod(nameof(BaseController.Redirect));
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = builder.IsAction(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAction_PrivateMethod_FromUserDefinedController()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleActionModelBuilder();
|
||||
var method = typeof(DerivedController).GetMethod(
|
||||
"PrivateMethod",
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = builder.IsAction(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAction_OperatorOverloadingMethod_FromOperatorOverloadingController()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleActionModelBuilder();
|
||||
var method = typeof(OperatorOverloadingController).GetMethod("op_Addition");
|
||||
Assert.NotNull(method);
|
||||
Assert.True(method.IsSpecialName);
|
||||
|
||||
// Act
|
||||
var isValid = builder.IsAction(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAction_GenericMethod_FromUserDefinedController()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleActionModelBuilder();
|
||||
var method = typeof(DerivedController).GetMethod("GenericMethod");
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = builder.IsAction(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAction_OverridenNonActionMethod()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleActionModelBuilder();
|
||||
var method = typeof(DerivedController).GetMethod("OverridenNonActionMethod");
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = builder.IsAction(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Equals")]
|
||||
[InlineData("GetHashCode")]
|
||||
[InlineData("MemberwiseClone")]
|
||||
[InlineData("ToString")]
|
||||
public void IsAction_OverriddenMethodsFromObjectClass(string methodName)
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleActionModelBuilder();
|
||||
var method = typeof(DerivedController).GetMethod(
|
||||
methodName,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = builder.IsAction(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("StaticMethod")]
|
||||
[InlineData("ProtectedStaticMethod")]
|
||||
[InlineData("PrivateStaticMethod")]
|
||||
public void IsAction_StaticMethods(string methodName)
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleActionModelBuilder();
|
||||
var method = typeof(DerivedController).GetMethod(
|
||||
methodName,
|
||||
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = builder.IsAction(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_ConventionallyRoutedAction_WithoutHttpConstraints()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder();
|
||||
var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo();
|
||||
var actionName = nameof(ConventionallyRoutedController.Edit);
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo.GetMethod(actionName));
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actions);
|
||||
Assert.Equal("Edit", action.ActionName);
|
||||
Assert.True(action.IsActionNameMatchRequired);
|
||||
Assert.Empty(action.HttpMethods);
|
||||
Assert.Null(action.AttributeRouteModel);
|
||||
Assert.Empty(action.Attributes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_ConventionallyRoutedAction_WithHttpConstraints()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder();
|
||||
var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo();
|
||||
var actionName = nameof(ConventionallyRoutedController.Update);
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo.GetMethod(actionName));
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actions);
|
||||
Assert.Contains("PUT", action.HttpMethods);
|
||||
Assert.Contains("PATCH", action.HttpMethods);
|
||||
|
||||
Assert.Equal("Update", action.ActionName);
|
||||
Assert.True(action.IsActionNameMatchRequired);
|
||||
Assert.Null(action.AttributeRouteModel);
|
||||
Assert.IsType<CustomHttpMethodsAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_ConventionallyRoutedActionWithHttpConstraints_AndInvalidRouteTemplateProvider()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder();
|
||||
var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo();
|
||||
var actionName = nameof(ConventionallyRoutedController.Delete);
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo.GetMethod(actionName));
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actions);
|
||||
Assert.Equal("Delete", action.ActionName);
|
||||
Assert.True(action.IsActionNameMatchRequired);
|
||||
|
||||
var httpMethod = Assert.Single(action.HttpMethods);
|
||||
Assert.Equal("DELETE", httpMethod);
|
||||
Assert.Null(action.AttributeRouteModel);
|
||||
|
||||
Assert.IsType<HttpDeleteAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_ConventionallyRoutedAction_WithMultipleHttpConstraints()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder();
|
||||
var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo();
|
||||
var actionName = nameof(ConventionallyRoutedController.Details);
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo.GetMethod(actionName));
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actions);
|
||||
Assert.Contains("GET", action.HttpMethods);
|
||||
Assert.Contains("POST", action.HttpMethods);
|
||||
Assert.Equal("Details", action.ActionName);
|
||||
Assert.True(action.IsActionNameMatchRequired);
|
||||
Assert.Null(action.AttributeRouteModel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_ConventionallyRoutedAction_WithMultipleOverlappingHttpConstraints()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder();
|
||||
var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo();
|
||||
var actionName = nameof(ConventionallyRoutedController.List);
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo.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.True(action.IsActionNameMatchRequired);
|
||||
Assert.Null(action.AttributeRouteModel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_AttributeRouteOnAction()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder();
|
||||
var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo();
|
||||
var actionName = nameof(NoRouteAttributeOnControllerController.Edit);
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo.GetMethod(actionName));
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actions);
|
||||
|
||||
Assert.Equal("Edit", action.ActionName);
|
||||
Assert.True(action.IsActionNameMatchRequired);
|
||||
|
||||
var httpMethod = Assert.Single(action.HttpMethods);
|
||||
Assert.Equal("POST", httpMethod);
|
||||
|
||||
Assert.NotNull(action.AttributeRouteModel);
|
||||
Assert.Equal("Change", action.AttributeRouteModel.Template);
|
||||
|
||||
Assert.IsType<HttpPostAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_AttributeRouteOnAction_RouteAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder();
|
||||
var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo();
|
||||
var actionName = nameof(NoRouteAttributeOnControllerController.Update);
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo.GetMethod(actionName));
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actions);
|
||||
|
||||
Assert.Equal("Update", action.ActionName);
|
||||
Assert.True(action.IsActionNameMatchRequired);
|
||||
|
||||
Assert.Empty(action.HttpMethods);
|
||||
|
||||
Assert.NotNull(action.AttributeRouteModel);
|
||||
Assert.Equal("Update", action.AttributeRouteModel.Template);
|
||||
|
||||
Assert.IsType<RouteAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_AttributeRouteOnAction_AcceptVerbsAttributeWithTemplate()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder();
|
||||
var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo();
|
||||
var actionName = nameof(NoRouteAttributeOnControllerController.List);
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo.GetMethod(actionName));
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actions);
|
||||
|
||||
Assert.Equal("List", action.ActionName);
|
||||
Assert.True(action.IsActionNameMatchRequired);
|
||||
|
||||
Assert.Equal(new[] { "GET", "HEAD" }, action.HttpMethods.OrderBy(m => m, StringComparer.Ordinal));
|
||||
|
||||
Assert.NotNull(action.AttributeRouteModel);
|
||||
Assert.Equal("ListAll", action.AttributeRouteModel.Template);
|
||||
|
||||
Assert.IsType<AcceptVerbsAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_AttributeRouteOnAction_CreatesOneActionInforPerRouteTemplate()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder();
|
||||
var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo();
|
||||
var actionName = nameof(NoRouteAttributeOnControllerController.Index);
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo.GetMethod(actionName));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, actions.Count());
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
Assert.Equal("Index", action.ActionName);
|
||||
Assert.True(action.IsActionNameMatchRequired);
|
||||
|
||||
Assert.NotNull(action.AttributeRouteModel);
|
||||
}
|
||||
|
||||
var list = Assert.Single(actions, ai => ai.AttributeRouteModel.Template.Equals("List"));
|
||||
var listMethod = Assert.Single(list.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);
|
||||
Assert.Equal("GET", allMethod);
|
||||
Assert.IsType<HttpGetAttribute>(Assert.Single(all.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_NoRouteOnController_AllowsConventionallyRoutedActions_OnTheSameController()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder();
|
||||
var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo();
|
||||
var actionName = nameof(NoRouteAttributeOnControllerController.Remove);
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo.GetMethod(actionName));
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actions);
|
||||
|
||||
Assert.Equal("Remove", action.ActionName);
|
||||
Assert.True(action.IsActionNameMatchRequired);
|
||||
|
||||
Assert.Empty(action.HttpMethods);
|
||||
|
||||
Assert.Null(action.AttributeRouteModel);
|
||||
|
||||
Assert.Empty(action.Attributes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(SingleRouteAttributeController))]
|
||||
[InlineData(typeof(MultipleRouteAttributeController))]
|
||||
public void GetActions_RouteAttributeOnController_CreatesAttributeRoute_ForNonAttributedActions(Type controller)
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder();
|
||||
var typeInfo = controller.GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo.GetMethod("Delete"));
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actions);
|
||||
|
||||
Assert.Equal("Delete", action.ActionName);
|
||||
Assert.True(action.IsActionNameMatchRequired);
|
||||
|
||||
Assert.Empty(action.HttpMethods);
|
||||
|
||||
Assert.Null(action.AttributeRouteModel);
|
||||
|
||||
Assert.Empty(action.Attributes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(SingleRouteAttributeController))]
|
||||
[InlineData(typeof(MultipleRouteAttributeController))]
|
||||
public void GetActions_RouteOnController_CreatesOneActionInforPerRouteTemplateOnAction(Type controller)
|
||||
{
|
||||
// Arrange
|
||||
var builder = new DefaultActionModelBuilder();
|
||||
var typeInfo = controller.GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var actions = builder.BuildActionModels(typeInfo.GetMethod("Index"));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, actions.Count());
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
Assert.Equal("Index", action.ActionName);
|
||||
Assert.True(action.IsActionNameMatchRequired);
|
||||
|
||||
var httpMethod = Assert.Single(action.HttpMethods);
|
||||
Assert.Equal("GET", httpMethod);
|
||||
|
||||
Assert.NotNull(action.AttributeRouteModel.Template);
|
||||
|
||||
Assert.IsType<HttpGetAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
Assert.Single(actions, ai => ai.AttributeRouteModel.Template.Equals("List"));
|
||||
Assert.Single(actions, ai => ai.AttributeRouteModel.Template.Equals("All"));
|
||||
}
|
||||
|
||||
private class AccessibleActionModelBuilder : DefaultActionModelBuilder
|
||||
{
|
||||
public new bool IsAction([NotNull]MethodInfo methodInfo)
|
||||
{
|
||||
return base.IsAction(methodInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private class BaseController : Controller
|
||||
{
|
||||
public void GetFromBase() // Valid action method.
|
||||
{
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
public virtual void OverridenNonActionMethod()
|
||||
{
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
public virtual void NewMethod()
|
||||
{
|
||||
}
|
||||
|
||||
public override RedirectResult Redirect(string url)
|
||||
{
|
||||
return base.Redirect(url + "#RedirectOverride");
|
||||
}
|
||||
}
|
||||
|
||||
private class DerivedController : BaseController
|
||||
{
|
||||
public void GetFromDerived() // Valid action method.
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public override void OverridenNonActionMethod()
|
||||
{
|
||||
}
|
||||
|
||||
public new void NewMethod() // Valid action method.
|
||||
{
|
||||
}
|
||||
|
||||
public void GenericMethod<T>()
|
||||
{
|
||||
}
|
||||
|
||||
private void PrivateMethod()
|
||||
{
|
||||
}
|
||||
|
||||
public static void StaticMethod()
|
||||
{
|
||||
}
|
||||
|
||||
protected static void ProtectedStaticMethod()
|
||||
{
|
||||
}
|
||||
|
||||
private static void PrivateStaticMethod()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class OperatorOverloadingController : Mvc.Controller
|
||||
{
|
||||
public static OperatorOverloadingController operator +(
|
||||
OperatorOverloadingController c1,
|
||||
OperatorOverloadingController c2)
|
||||
{
|
||||
return new OperatorOverloadingController();
|
||||
}
|
||||
}
|
||||
|
||||
private class NoRouteAttributeOnControllerController : Controller
|
||||
{
|
||||
[HttpGet("All")]
|
||||
[HttpPost("List")]
|
||||
public void Index() { }
|
||||
|
||||
[HttpPost("Change")]
|
||||
public void Edit() { }
|
||||
|
||||
public void Remove() { }
|
||||
|
||||
[Route("Update")]
|
||||
public void Update() { }
|
||||
|
||||
[AcceptVerbs("GET", "HEAD", Route = "ListAll")]
|
||||
public void List() { }
|
||||
}
|
||||
|
||||
[Route("Products")]
|
||||
private class SingleRouteAttributeController : Controller
|
||||
{
|
||||
[HttpGet("All")]
|
||||
[HttpGet("List")]
|
||||
public void Index() { }
|
||||
|
||||
public void Delete() { }
|
||||
}
|
||||
|
||||
[Route("Products")]
|
||||
[Route("Items")]
|
||||
private class MultipleRouteAttributeController : Controller
|
||||
{
|
||||
[HttpGet("All")]
|
||||
[HttpGet("List")]
|
||||
public void Index() { }
|
||||
|
||||
public void Delete() { }
|
||||
}
|
||||
|
||||
// Here the constraints on the methods are acting as an IActionHttpMethodProvider and
|
||||
// not as an IRouteTemplateProvider given that there is no RouteAttribute
|
||||
// on the controller and the template for all the constraints on a method is null.
|
||||
private class ConventionallyRoutedController : Controller
|
||||
{
|
||||
public void Edit() { }
|
||||
|
||||
[CustomHttpMethods("PUT", "PATCH")]
|
||||
public void Update() { }
|
||||
|
||||
[HttpDelete]
|
||||
public void Delete() { }
|
||||
|
||||
[HttpPost]
|
||||
[HttpGet]
|
||||
public void Details() { }
|
||||
|
||||
[HttpGet]
|
||||
[HttpPut]
|
||||
[AcceptVerbs("GET", "POST")]
|
||||
public void List() { }
|
||||
}
|
||||
|
||||
private class CustomHttpMethodsAttribute : Attribute, IActionHttpMethodProvider
|
||||
{
|
||||
private readonly string[] _methods;
|
||||
|
||||
public CustomHttpMethodsAttribute(params string[] methods)
|
||||
{
|
||||
_methods = methods;
|
||||
}
|
||||
|
||||
public IEnumerable<string> HttpMethods
|
||||
{
|
||||
get
|
||||
{
|
||||
return _methods;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.ApplicationModel.DefaultControllerModelBuilderTestControllers;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ApplicationModel
|
||||
{
|
||||
public class DefaultControllerModelBuilderTest
|
||||
{
|
||||
[Fact]
|
||||
public void IsController_UserDefinedClass()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleControllerModelBuilder();
|
||||
var typeInfo = typeof(StoreController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = builder.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.True(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_FrameworkControllerClass()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleControllerModelBuilder();
|
||||
var typeInfo = typeof(Controller).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = builder.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.False(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_UserDefinedControllerClass()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleControllerModelBuilder();
|
||||
var typeInfo = typeof(DefaultControllerModelBuilderTestControllers.Controller).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = builder.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.False(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_Interface()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleControllerModelBuilder();
|
||||
var typeInfo = typeof(IController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = builder.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.False(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_AbstractClass()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleControllerModelBuilder();
|
||||
var typeInfo = typeof(AbstractController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = builder.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.False(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_DerivedAbstractClass()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleControllerModelBuilder();
|
||||
var typeInfo = typeof(DerivedAbstractController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = builder.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.True(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_OpenGenericClass()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleControllerModelBuilder();
|
||||
var typeInfo = typeof(OpenGenericController<>).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = builder.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.False(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_ClosedGenericClass()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleControllerModelBuilder();
|
||||
var typeInfo = typeof(OpenGenericController<string>).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = builder.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.False(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_DerivedGenericClass()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleControllerModelBuilder();
|
||||
var typeInfo = typeof(DerivedGenericController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = builder.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.True(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_Poco_WithNamingConvention()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleControllerModelBuilder();
|
||||
var typeInfo = typeof(PocoController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = builder.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.True(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_NoControllerSuffix()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new AccessibleControllerModelBuilder();
|
||||
var typeInfo = typeof(NoSuffix).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = builder.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.True(isController);
|
||||
}
|
||||
|
||||
private class AccessibleControllerModelBuilder : DefaultControllerModelBuilder
|
||||
{
|
||||
public AccessibleControllerModelBuilder()
|
||||
: base(new DefaultActionModelBuilder())
|
||||
{
|
||||
}
|
||||
|
||||
public new bool IsController([NotNull]TypeInfo typeInfo)
|
||||
{
|
||||
return base.IsController(typeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These controllers are used to test the DefaultActionDiscoveryConventions implementation
|
||||
// which REQUIRES that they be public top-level classes. To avoid having to stub out the
|
||||
// implementation of this class to test it, they are just top level classes. Don't reuse
|
||||
// these outside this test - find a better way or use nested classes to keep the tests
|
||||
// independent.
|
||||
namespace Microsoft.AspNet.Mvc.ApplicationModel.DefaultControllerModelBuilderTestControllers
|
||||
{
|
||||
public abstract class AbstractController : Mvc.Controller
|
||||
{
|
||||
}
|
||||
|
||||
public class DerivedAbstractController : AbstractController
|
||||
{
|
||||
}
|
||||
|
||||
public class StoreController : Mvc.Controller
|
||||
{
|
||||
}
|
||||
|
||||
public class Controller
|
||||
{
|
||||
}
|
||||
|
||||
public class OpenGenericController<T>
|
||||
{
|
||||
}
|
||||
|
||||
public class DerivedGenericController : OpenGenericController<string>
|
||||
{
|
||||
}
|
||||
|
||||
public interface IController
|
||||
{
|
||||
}
|
||||
|
||||
public class NoSuffix : Mvc.Controller
|
||||
{
|
||||
}
|
||||
|
||||
public class PocoController
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -767,8 +767,6 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
"AttributeAndNonAttributeRoutedActionsOnSameMethodController.Method' - Template: 'AttributeRouted'" + Environment.NewLine +
|
||||
"Action: 'Microsoft.AspNet.Mvc.Test.ControllerActionDescriptorProviderTests+" +
|
||||
"AttributeAndNonAttributeRoutedActionsOnSameMethodController.Method' - Template: '(none)'" + Environment.NewLine +
|
||||
"Action: 'Microsoft.AspNet.Mvc.Test.ControllerActionDescriptorProviderTests+" +
|
||||
"AttributeAndNonAttributeRoutedActionsOnSameMethodController.Method' - Template: '(none)'" + Environment.NewLine +
|
||||
"A method 'Microsoft.AspNet.Mvc.Test.ControllerActionDescriptorProviderTests+" +
|
||||
"AttributeAndNonAttributeRoutedActionsOnSameMethodController.Method' that defines attribute routed actions must not" +
|
||||
" have attributes that implement 'Microsoft.AspNet.Mvc.IActionHttpMethodProvider' and do not implement" +
|
||||
|
|
@ -1153,7 +1151,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
parameter.Attributes.Add(parameterConvention.Object);
|
||||
|
||||
// Act
|
||||
provider.ApplyConventions(model);
|
||||
ApplicationModelConventions.ApplyConventions(model, options.Options.ApplicationModelConventions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(4, sequence);
|
||||
|
|
@ -1306,7 +1304,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
TypeInfo controllerTypeInfo,
|
||||
IEnumerable<IFilter> filters = null)
|
||||
{
|
||||
var conventions = new StaticActionDiscoveryConventions(controllerTypeInfo);
|
||||
var modelBuilder = new StaticControllerModelBuilder(controllerTypeInfo);
|
||||
|
||||
var assemblyProvider = new Mock<IAssemblyProvider>();
|
||||
assemblyProvider
|
||||
|
|
@ -1315,7 +1313,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
|
||||
var provider = new ControllerActionDescriptorProvider(
|
||||
assemblyProvider.Object,
|
||||
conventions,
|
||||
modelBuilder,
|
||||
new TestGlobalFilterProvider(filters),
|
||||
new MockMvcOptionsAccessor());
|
||||
|
||||
|
|
@ -1325,7 +1323,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
private ControllerActionDescriptorProvider GetProvider(
|
||||
params TypeInfo[] controllerTypeInfo)
|
||||
{
|
||||
var conventions = new StaticActionDiscoveryConventions(controllerTypeInfo);
|
||||
var modelBuilder = new StaticControllerModelBuilder(controllerTypeInfo);
|
||||
|
||||
var assemblyProvider = new Mock<IAssemblyProvider>();
|
||||
assemblyProvider
|
||||
|
|
@ -1334,7 +1332,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
|
||||
var provider = new ControllerActionDescriptorProvider(
|
||||
assemblyProvider.Object,
|
||||
conventions,
|
||||
modelBuilder,
|
||||
new TestGlobalFilterProvider(),
|
||||
new MockMvcOptionsAccessor());
|
||||
|
||||
|
|
@ -1345,7 +1343,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
TypeInfo type,
|
||||
IOptions<MvcOptions> options)
|
||||
{
|
||||
var conventions = new StaticActionDiscoveryConventions(type);
|
||||
var modelBuilder = new StaticControllerModelBuilder(type);
|
||||
|
||||
var assemblyProvider = new Mock<IAssemblyProvider>();
|
||||
assemblyProvider
|
||||
|
|
@ -1354,14 +1352,14 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
|
||||
return new ControllerActionDescriptorProvider(
|
||||
assemblyProvider.Object,
|
||||
conventions,
|
||||
modelBuilder,
|
||||
new TestGlobalFilterProvider(),
|
||||
options);
|
||||
}
|
||||
|
||||
private IEnumerable<ActionDescriptor> GetDescriptors(params TypeInfo[] controllerTypeInfos)
|
||||
{
|
||||
var conventions = new StaticActionDiscoveryConventions(controllerTypeInfos);
|
||||
var modelBuilder = new StaticControllerModelBuilder(controllerTypeInfos);
|
||||
|
||||
var assemblyProvider = new Mock<IAssemblyProvider>();
|
||||
assemblyProvider
|
||||
|
|
@ -1370,7 +1368,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
|
||||
var provider = new ControllerActionDescriptorProvider(
|
||||
assemblyProvider.Object,
|
||||
conventions,
|
||||
modelBuilder,
|
||||
new TestGlobalFilterProvider(),
|
||||
new MockMvcOptionsAccessor());
|
||||
|
||||
|
|
|
|||
|
|
@ -1,188 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNET50
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.DependencyInjection.NestedProviders;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class DefaultActionDiscoveryConventionsActionSelectionTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ActionSelection_ActionSelectedByName()
|
||||
{
|
||||
// Arrange
|
||||
var routeContext = new RouteContext(GetHttpContext("GET"));
|
||||
routeContext.RouteData.Values = new Dictionary<string, object>
|
||||
{
|
||||
{ "controller", "RpcOnly" },
|
||||
{ "action", "Index" }
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await InvokeActionSelector(routeContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Index", result.Name);
|
||||
}
|
||||
|
||||
// Uses custom conventions to map a web-api-style action
|
||||
[Fact]
|
||||
public async Task ActionSelection_ChangeDefaultConventionPicksCustomMethodForPost_CutomMethodIsSelected()
|
||||
{
|
||||
// Arrange
|
||||
var routeContext = new RouteContext(GetHttpContext("POST"));
|
||||
routeContext.RouteData.Values = new Dictionary<string, object>
|
||||
{
|
||||
{ "controller", "RpcOnly" }
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await InvokeActionSelector(routeContext, new CustomActionConvention());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("PostSomething", result.Name);
|
||||
}
|
||||
|
||||
private async Task<ActionDescriptor> InvokeActionSelector(RouteContext context)
|
||||
{
|
||||
var controllerTypeInfos = typeof(DefaultActionDiscoveryConventionsActionSelectionTests)
|
||||
.GetNestedTypes(BindingFlags.NonPublic)
|
||||
.Select(ct => ct.GetTypeInfo())
|
||||
.ToArray();
|
||||
|
||||
var conventions = new StaticActionDiscoveryConventions(controllerTypeInfos);
|
||||
return await InvokeActionSelector(context, conventions);
|
||||
}
|
||||
|
||||
private async Task<ActionDescriptor> InvokeActionSelector(RouteContext context,
|
||||
DefaultActionDiscoveryConventions actionDiscoveryConventions)
|
||||
{
|
||||
var actionDescriptorProvider = GetActionDescriptorProvider(actionDiscoveryConventions);
|
||||
var descriptorProvider =
|
||||
new NestedProviderManager<ActionDescriptorProviderContext>(new[] { actionDescriptorProvider });
|
||||
|
||||
var serviceContainer = new ServiceContainer();
|
||||
serviceContainer.AddService(typeof(INestedProviderManager<ActionDescriptorProviderContext>),
|
||||
descriptorProvider);
|
||||
|
||||
var actionCollectionDescriptorProvider = new DefaultActionDescriptorsCollectionProvider(serviceContainer);
|
||||
var decisionTreeProvider = new ActionSelectorDecisionTreeProvider(actionCollectionDescriptorProvider);
|
||||
|
||||
var actionConstraintProvider = new NestedProviderManager<ActionConstraintProviderContext>(
|
||||
new INestedProvider<ActionConstraintProviderContext>[]
|
||||
{
|
||||
new DefaultActionConstraintProvider(serviceContainer),
|
||||
});
|
||||
|
||||
var defaultActionSelector = new DefaultActionSelector(
|
||||
actionCollectionDescriptorProvider,
|
||||
decisionTreeProvider,
|
||||
actionConstraintProvider,
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
return await defaultActionSelector.SelectAsync(context);
|
||||
}
|
||||
|
||||
private ControllerActionDescriptorProvider GetActionDescriptorProvider(DefaultActionDiscoveryConventions actionDiscoveryConventions)
|
||||
{
|
||||
var assemblies = new Assembly[] { typeof(DefaultActionDiscoveryConventionsActionSelectionTests).GetTypeInfo().Assembly, };
|
||||
var AssemblyProvider = new Mock<IAssemblyProvider>();
|
||||
AssemblyProvider.SetupGet(x => x.CandidateAssemblies).Returns(assemblies);
|
||||
return new ControllerActionDescriptorProvider(
|
||||
AssemblyProvider.Object,
|
||||
actionDiscoveryConventions,
|
||||
new TestGlobalFilterProvider(),
|
||||
new MockMvcOptionsAccessor());
|
||||
}
|
||||
|
||||
private static HttpContext GetHttpContext(string httpMethod)
|
||||
{
|
||||
var request = new Mock<HttpRequest>();
|
||||
var headers = new Mock<IHeaderDictionary>();
|
||||
request.SetupGet(r => r.Headers).Returns(headers.Object);
|
||||
request.SetupGet(x => x.Method).Returns(httpMethod);
|
||||
var httpContext = new Mock<HttpContext>();
|
||||
httpContext.SetupGet(c => c.Request).Returns(request.Object);
|
||||
return httpContext.Object;
|
||||
}
|
||||
|
||||
private class CustomActionConvention : DefaultActionDiscoveryConventions
|
||||
{
|
||||
public override bool IsController([NotNull]TypeInfo typeInfo)
|
||||
{
|
||||
return
|
||||
typeof(DefaultActionDiscoveryConventionsActionSelectionTests)
|
||||
.GetNestedTypes(BindingFlags.NonPublic)
|
||||
.Select(ct => ct.GetTypeInfo())
|
||||
.Contains(typeInfo);
|
||||
}
|
||||
|
||||
public override IEnumerable<ActionInfo> GetActions([NotNull]MethodInfo methodInfo, [NotNull]TypeInfo controllerTypeInfo)
|
||||
{
|
||||
var actions = new List<ActionInfo>(
|
||||
base.GetActions(methodInfo, controllerTypeInfo) ??
|
||||
new List<ActionInfo>());
|
||||
|
||||
if (methodInfo.Name == "PostSomething")
|
||||
{
|
||||
actions[0].HttpMethods = new string[] { "POST" };
|
||||
actions[0].RequireActionNameMatch = false;
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
private class RpcOnlyController
|
||||
{
|
||||
public void Index()
|
||||
{
|
||||
}
|
||||
|
||||
public void GetSomething()
|
||||
{
|
||||
}
|
||||
|
||||
public void PutSomething()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostSomething()
|
||||
{
|
||||
}
|
||||
|
||||
public void DeleteSomething()
|
||||
{
|
||||
}
|
||||
|
||||
public void PatchSomething()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class AmbiguousController
|
||||
{
|
||||
public void Index(int i)
|
||||
{ }
|
||||
|
||||
public void Index(string s)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1,811 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.DefaultActionDiscoveryConventionsControllers;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class DefaultActionDiscoveryConventionsTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("GetFromDerived", true)]
|
||||
[InlineData("NewMethod", true)] // "NewMethod" is a public method declared with keyword "new".
|
||||
[InlineData("GetFromBase", true)]
|
||||
public void IsValidActionMethod_WithInheritedMethods(string methodName, bool expected)
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var method = typeof(DerivedController).GetMethod(methodName);
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = conventions.IsValidActionMethod(method);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidActionMethod_OverridenMethodControllerClass()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var method = typeof(BaseController).GetMethod("Redirect");
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = conventions.IsValidActionMethod(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidActionMethod_PrivateMethod_FromUserDefinedController()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var method = typeof(DerivedController).GetMethod(
|
||||
"PrivateMethod",
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = conventions.IsValidActionMethod(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidActionMethod_OperatorOverloadingMethod_FromOperatorOverloadingController()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var method = typeof(OperatorOverloadingController).GetMethod("op_Addition");
|
||||
Assert.NotNull(method);
|
||||
Assert.True(method.IsSpecialName);
|
||||
|
||||
// Act
|
||||
var isValid = conventions.IsValidActionMethod(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidActionMethod_GenericMethod_FromUserDefinedController()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var method = typeof(DerivedController).GetMethod("GenericMethod");
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = conventions.IsValidActionMethod(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidActionMethod_OverridenNonActionMethod()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var method = typeof(DerivedController).GetMethod("OverridenNonActionMethod");
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = conventions.IsValidActionMethod(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Equals")]
|
||||
[InlineData("GetHashCode")]
|
||||
[InlineData("MemberwiseClone")]
|
||||
[InlineData("ToString")]
|
||||
public void IsValidActionMethod_OverriddenMethodsFromObjectClass(string methodName)
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var method = typeof(DerivedController).GetMethod(
|
||||
methodName,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = conventions.IsValidActionMethod(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("StaticMethod")]
|
||||
[InlineData("ProtectedStaticMethod")]
|
||||
[InlineData("PrivateStaticMethod")]
|
||||
public void IsValidActionMethod_StaticMethods(string methodName)
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var method = typeof(DerivedController).GetMethod(
|
||||
methodName,
|
||||
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
Assert.NotNull(method);
|
||||
|
||||
// Act
|
||||
var isValid = conventions.IsValidActionMethod(method);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_ConventionallyRoutedAction_WithoutHttpConstraints()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo();
|
||||
var actionName = nameof(ConventionallyRoutedController.Edit);
|
||||
|
||||
// Act
|
||||
var actionInfos = conventions.GetActions(typeInfo.GetMethod(actionName), typeInfo);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actionInfos);
|
||||
Assert.Equal("Edit", action.ActionName);
|
||||
Assert.True(action.RequireActionNameMatch);
|
||||
Assert.Null(action.HttpMethods);
|
||||
Assert.Null(action.AttributeRoute);
|
||||
Assert.Empty(action.Attributes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_ConventionallyRoutedAction_WithHttpConstraints()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo();
|
||||
var actionName = nameof(ConventionallyRoutedController.Update);
|
||||
|
||||
// Act
|
||||
var actionInfos = conventions.GetActions(typeInfo.GetMethod(actionName), typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, actionInfos.Count());
|
||||
Assert.Single(actionInfos, a => a.HttpMethods.Contains("PUT"));
|
||||
Assert.Single(actionInfos, a => a.HttpMethods.Contains("PATCH"));
|
||||
|
||||
foreach (var action in actionInfos)
|
||||
{
|
||||
Assert.Equal("Update", action.ActionName);
|
||||
Assert.True(action.RequireActionNameMatch);
|
||||
Assert.Null(action.AttributeRoute);
|
||||
Assert.IsType<CustomHttpMethodsAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_ConventionallyRoutedActionWithHttpConstraints_AndInvalidRouteTemplateProvider()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo();
|
||||
var actionName = nameof(ConventionallyRoutedController.Delete);
|
||||
|
||||
// Act
|
||||
var actionInfos = conventions.GetActions(typeInfo.GetMethod(actionName), typeInfo);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actionInfos);
|
||||
Assert.Equal("Delete", action.ActionName);
|
||||
Assert.True(action.RequireActionNameMatch);
|
||||
|
||||
var httpMethod = Assert.Single(action.HttpMethods);
|
||||
Assert.Equal("DELETE", httpMethod);
|
||||
Assert.Null(action.AttributeRoute);
|
||||
|
||||
Assert.IsType<HttpDeleteAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_ConventionallyRoutedAction_WithMultipleHttpConstraints()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo();
|
||||
var actionName = nameof(ConventionallyRoutedController.Details);
|
||||
|
||||
// Act
|
||||
var actionInfos = conventions.GetActions(typeInfo.GetMethod(actionName), typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, actionInfos.Count());
|
||||
Assert.Single(actionInfos, a => a.HttpMethods.Contains("GET"));
|
||||
Assert.Single(actionInfos, a => a.HttpMethods.Contains("POST"));
|
||||
|
||||
foreach (var action in actionInfos)
|
||||
{
|
||||
|
||||
Assert.Equal("Details", action.ActionName);
|
||||
Assert.True(action.RequireActionNameMatch);
|
||||
Assert.Null(action.AttributeRoute);
|
||||
|
||||
Assert.Equal(2, action.Attributes.Length);
|
||||
Assert.Single(action.Attributes, a => a is HttpGetAttribute);
|
||||
Assert.Single(action.Attributes, a => a is HttpPostAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_ConventionallyRoutedAction_WithMultipleOverlappingHttpConstraints()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(ConventionallyRoutedController).GetTypeInfo();
|
||||
var actionName = nameof(ConventionallyRoutedController.List);
|
||||
|
||||
// Act
|
||||
var actionInfos = conventions.GetActions(typeInfo.GetMethod(actionName), typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, actionInfos.Count());
|
||||
Assert.Single(actionInfos, a => a.HttpMethods.Contains("GET"));
|
||||
Assert.Single(actionInfos, a => a.HttpMethods.Contains("POST"));
|
||||
Assert.Single(actionInfos, a => a.HttpMethods.Contains("PUT"));
|
||||
|
||||
foreach (var action in actionInfos)
|
||||
{
|
||||
Assert.Equal("List", action.ActionName);
|
||||
Assert.True(action.RequireActionNameMatch);
|
||||
Assert.Null(action.AttributeRoute);
|
||||
|
||||
Assert.Equal(3, action.Attributes.Length);
|
||||
Assert.Single(action.Attributes, a => a is HttpPutAttribute);
|
||||
Assert.Single(action.Attributes, a => a is HttpGetAttribute);
|
||||
Assert.Single(action.Attributes, a => a is AcceptVerbsAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_AttributeRouteOnAction()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo();
|
||||
var actionName = nameof(NoRouteAttributeOnControllerController.Edit);
|
||||
|
||||
// Act
|
||||
var actionInfos = conventions.GetActions(typeInfo.GetMethod(actionName), typeInfo);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actionInfos);
|
||||
|
||||
Assert.Equal("Edit", action.ActionName);
|
||||
Assert.True(action.RequireActionNameMatch);
|
||||
|
||||
var httpMethod = Assert.Single(action.HttpMethods);
|
||||
Assert.Equal("POST", httpMethod);
|
||||
|
||||
Assert.NotNull(action.AttributeRoute);
|
||||
Assert.Equal("Change", action.AttributeRoute.Template);
|
||||
|
||||
Assert.IsType<HttpPostAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_AttributeRouteOnAction_RouteAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo();
|
||||
var actionName = nameof(NoRouteAttributeOnControllerController.Update);
|
||||
|
||||
// Act
|
||||
var actionInfos = conventions.GetActions(typeInfo.GetMethod(actionName), typeInfo);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actionInfos);
|
||||
|
||||
Assert.Equal("Update", action.ActionName);
|
||||
Assert.True(action.RequireActionNameMatch);
|
||||
|
||||
Assert.Null(action.HttpMethods);
|
||||
|
||||
Assert.NotNull(action.AttributeRoute);
|
||||
Assert.Equal("Update", action.AttributeRoute.Template);
|
||||
|
||||
Assert.IsType<RouteAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_AttributeRouteOnAction_AcceptVerbsAttributeWithTemplate()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo();
|
||||
var actionName = nameof(NoRouteAttributeOnControllerController.List);
|
||||
|
||||
// Act
|
||||
var actionInfos = conventions.GetActions(typeInfo.GetMethod(actionName), typeInfo);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actionInfos);
|
||||
|
||||
Assert.Equal("List", action.ActionName);
|
||||
Assert.True(action.RequireActionNameMatch);
|
||||
|
||||
Assert.Equal(new[] { "GET", "HEAD" }, action.HttpMethods.OrderBy(m => m, StringComparer.Ordinal));
|
||||
|
||||
Assert.NotNull(action.AttributeRoute);
|
||||
Assert.Equal("ListAll", action.AttributeRoute.Template);
|
||||
|
||||
Assert.IsType<AcceptVerbsAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_AttributeRouteOnAction_CreatesOneActionInforPerRouteTemplate()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo();
|
||||
var actionName = nameof(NoRouteAttributeOnControllerController.Index);
|
||||
|
||||
// Act
|
||||
var actionInfos = conventions.GetActions(typeInfo.GetMethod(actionName), typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, actionInfos.Count());
|
||||
|
||||
foreach (var action in actionInfos)
|
||||
{
|
||||
Assert.Equal("Index", action.ActionName);
|
||||
Assert.True(action.RequireActionNameMatch);
|
||||
|
||||
Assert.NotNull(action.AttributeRoute);
|
||||
}
|
||||
|
||||
var list = Assert.Single(actionInfos, ai => ai.AttributeRoute.Template.Equals("List"));
|
||||
var listMethod = Assert.Single(list.HttpMethods);
|
||||
Assert.Equal("POST", listMethod);
|
||||
Assert.IsType<HttpPostAttribute>(Assert.Single(list.Attributes));
|
||||
|
||||
var all = Assert.Single(actionInfos, ai => ai.AttributeRoute.Template.Equals("All"));
|
||||
var allMethod = Assert.Single(all.HttpMethods);
|
||||
Assert.Equal("GET", allMethod);
|
||||
Assert.IsType<HttpGetAttribute>(Assert.Single(all.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_NoRouteOnController_AllowsConventionallyRoutedActions_OnTheSameController()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(NoRouteAttributeOnControllerController).GetTypeInfo();
|
||||
var actionName = nameof(NoRouteAttributeOnControllerController.Remove);
|
||||
|
||||
// Act
|
||||
var actionInfos = conventions.GetActions(typeInfo.GetMethod(actionName), typeInfo);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actionInfos);
|
||||
|
||||
Assert.Equal("Remove", action.ActionName);
|
||||
Assert.True(action.RequireActionNameMatch);
|
||||
|
||||
Assert.Null(action.HttpMethods);
|
||||
|
||||
Assert.Null(action.AttributeRoute);
|
||||
|
||||
Assert.Empty(action.Attributes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(SingleRouteAttributeController))]
|
||||
[InlineData(typeof(MultipleRouteAttributeController))]
|
||||
public void GetActions_RouteAttributeOnController_CreatesAttributeRoute_ForNonAttributedActions(Type controller)
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = controller.GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var actionInfos = conventions.GetActions(typeInfo.GetMethod("Delete"), typeInfo);
|
||||
|
||||
// Assert
|
||||
var action = Assert.Single(actionInfos);
|
||||
|
||||
Assert.Equal("Delete", action.ActionName);
|
||||
Assert.True(action.RequireActionNameMatch);
|
||||
|
||||
Assert.Null(action.HttpMethods);
|
||||
|
||||
Assert.Null(action.AttributeRoute);
|
||||
|
||||
Assert.Empty(action.Attributes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(SingleRouteAttributeController))]
|
||||
[InlineData(typeof(MultipleRouteAttributeController))]
|
||||
public void GetActions_RouteOnController_CreatesOneActionInforPerRouteTemplateOnAction(Type controller)
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = controller.GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var actionInfos = conventions.GetActions(typeInfo.GetMethod("Index"), typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, actionInfos.Count());
|
||||
|
||||
foreach (var action in actionInfos)
|
||||
{
|
||||
Assert.Equal("Index", action.ActionName);
|
||||
Assert.True(action.RequireActionNameMatch);
|
||||
|
||||
var httpMethod = Assert.Single(action.HttpMethods);
|
||||
Assert.Equal("GET", httpMethod);
|
||||
|
||||
Assert.NotNull(action.AttributeRoute);
|
||||
|
||||
Assert.IsType<HttpGetAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
Assert.Single(actionInfos, ai => ai.AttributeRoute.Template.Equals("List"));
|
||||
Assert.Single(actionInfos, ai => ai.AttributeRoute.Template.Equals("All"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_UserDefinedClass()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(BaseController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = conventions.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.True(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_FrameworkControllerClass()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(Controller).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = conventions.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.False(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_UserDefinedControllerClass()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(DefaultActionDiscoveryConventionsControllers.Controller).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = conventions.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.False(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_Interface()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(IController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = conventions.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.False(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_AbstractClass()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(AbstractController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = conventions.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.False(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_DerivedAbstractClass()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(DerivedAbstractController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = conventions.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.True(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_OpenGenericClass()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(OpenGenericController<>).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = conventions.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.False(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_ClosedGenericClass()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(OpenGenericController<string>).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = conventions.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.False(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_DerivedGenericClass()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(DerivedGenericController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = conventions.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.True(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_Poco_WithNamingConvention()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(PocoController).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = conventions.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.True(isController);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsController_NoControllerSuffix()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new DefaultActionDiscoveryConventions();
|
||||
var typeInfo = typeof(NoSuffix).GetTypeInfo();
|
||||
|
||||
// Act
|
||||
var isController = conventions.IsController(typeInfo);
|
||||
|
||||
// Assert
|
||||
Assert.True(isController);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These controllers are used to test the DefaultActionDiscoveryConventions implementation
|
||||
// which REQUIRES that they be public top-level classes. To avoid having to stub out the
|
||||
// implementation of this class to test it, they are just top level classes. Don't reuse
|
||||
// these outside this test - find a better way or use nested classes to keep the tests
|
||||
// independent.
|
||||
namespace Microsoft.AspNet.Mvc.DefaultActionDiscoveryConventionsControllers
|
||||
{
|
||||
public abstract class AbstractController : Mvc.Controller
|
||||
{
|
||||
}
|
||||
|
||||
public class DerivedAbstractController : AbstractController
|
||||
{
|
||||
}
|
||||
|
||||
public class BaseController : Mvc.Controller
|
||||
{
|
||||
public void GetFromBase() // Valid action method.
|
||||
{
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
public virtual void OverridenNonActionMethod()
|
||||
{
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
public virtual void NewMethod()
|
||||
{
|
||||
}
|
||||
|
||||
public override RedirectResult Redirect(string url)
|
||||
{
|
||||
return base.Redirect(url + "#RedirectOverride");
|
||||
}
|
||||
}
|
||||
|
||||
public class DerivedController : BaseController
|
||||
{
|
||||
public void GetFromDerived() // Valid action method.
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public override void OverridenNonActionMethod()
|
||||
{
|
||||
}
|
||||
|
||||
public new void NewMethod() // Valid action method.
|
||||
{
|
||||
}
|
||||
|
||||
public void GenericMethod<T>()
|
||||
{
|
||||
}
|
||||
|
||||
private void PrivateMethod()
|
||||
{
|
||||
}
|
||||
|
||||
public static void StaticMethod()
|
||||
{
|
||||
}
|
||||
|
||||
protected static void ProtectedStaticMethod()
|
||||
{
|
||||
}
|
||||
|
||||
private static void PrivateStaticMethod()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class Controller
|
||||
{
|
||||
}
|
||||
|
||||
public class OpenGenericController<T>
|
||||
{
|
||||
}
|
||||
|
||||
public class DerivedGenericController : OpenGenericController<string>
|
||||
{
|
||||
}
|
||||
|
||||
public interface IController
|
||||
{
|
||||
}
|
||||
|
||||
public class NoSuffix : Mvc.Controller
|
||||
{
|
||||
}
|
||||
|
||||
public class PocoController
|
||||
{
|
||||
}
|
||||
|
||||
public class OperatorOverloadingController : Mvc.Controller
|
||||
{
|
||||
public static OperatorOverloadingController operator +(
|
||||
OperatorOverloadingController c1,
|
||||
OperatorOverloadingController c2)
|
||||
{
|
||||
return new OperatorOverloadingController();
|
||||
}
|
||||
}
|
||||
|
||||
public class NoRouteAttributeOnControllerController : Controller
|
||||
{
|
||||
[HttpGet("All")]
|
||||
[HttpPost("List")]
|
||||
public void Index() { }
|
||||
|
||||
[HttpPost("Change")]
|
||||
public void Edit() { }
|
||||
|
||||
public void Remove() { }
|
||||
|
||||
[Route("Update")]
|
||||
public void Update() { }
|
||||
|
||||
[AcceptVerbs("GET", "HEAD", Route = "ListAll")]
|
||||
public void List() { }
|
||||
}
|
||||
|
||||
[Route("Products")]
|
||||
public class SingleRouteAttributeController : Controller
|
||||
{
|
||||
[HttpGet("All")]
|
||||
[HttpGet("List")]
|
||||
public void Index() { }
|
||||
|
||||
public void Delete() { }
|
||||
}
|
||||
|
||||
[Route("Products")]
|
||||
[Route("Items")]
|
||||
public class MultipleRouteAttributeController : Controller
|
||||
{
|
||||
[HttpGet("All")]
|
||||
[HttpGet("List")]
|
||||
public void Index() { }
|
||||
|
||||
public void Delete() { }
|
||||
}
|
||||
|
||||
// Here the constraints on the methods are acting as an IActionHttpMethodProvider and
|
||||
// not as an IRouteTemplateProvider given that there is no RouteAttribute
|
||||
// on the controller and the template for all the constraints on a method is null.
|
||||
public class ConventionallyRoutedController : Controller
|
||||
{
|
||||
public void Edit() { }
|
||||
|
||||
[CustomHttpMethods("PUT", "PATCH")]
|
||||
public void Update() { }
|
||||
|
||||
[HttpDelete]
|
||||
public void Delete() { }
|
||||
|
||||
[HttpPost]
|
||||
[HttpGet]
|
||||
public void Details() { }
|
||||
|
||||
[HttpGet]
|
||||
[HttpPut]
|
||||
[AcceptVerbs("GET", "POST")]
|
||||
public void List() { }
|
||||
}
|
||||
|
||||
public class CustomHttpMethodsAttribute : Attribute, IActionHttpMethodProvider
|
||||
{
|
||||
private readonly string[] _methods;
|
||||
|
||||
public CustomHttpMethodsAttribute(params string[] methods)
|
||||
{
|
||||
_methods = methods;
|
||||
}
|
||||
|
||||
public IEnumerable<string> HttpMethods
|
||||
{
|
||||
get
|
||||
{
|
||||
return _methods;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,13 @@ using System.Collections.Generic;
|
|||
using System.ComponentModel.Design;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.AspNet.Mvc.ApplicationModel;
|
||||
using Microsoft.AspNet.Mvc.Logging;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.DependencyInjection.NestedProviders;
|
||||
using Microsoft.Framework.Logging;
|
||||
|
|
@ -51,7 +54,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.Empty(values.ActionsMatchingRouteConstraints);
|
||||
Assert.Empty(values.ActionsMatchingActionConstraints);
|
||||
Assert.Empty(values.FinalMatches);
|
||||
Assert.Null(values.SelectedAction);
|
||||
Assert.Null(values.SelectedAction);
|
||||
Assert.DoesNotThrow(() => values.Summary);
|
||||
}
|
||||
|
||||
|
|
@ -563,7 +566,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
// Arrange
|
||||
var expectedMessage =
|
||||
"Multiple actions matched. " +
|
||||
"Multiple actions matched. " +
|
||||
"The following actions matched route data and had all constraints satisfied:" + Environment.NewLine +
|
||||
Environment.NewLine +
|
||||
"Ambiguous1" + Environment.NewLine +
|
||||
|
|
@ -595,6 +598,200 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.Equal(expectedMessage, ex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("PUT")]
|
||||
[InlineData("POST")]
|
||||
[InlineData("DELETE")]
|
||||
[InlineData("PATCH")]
|
||||
public async Task HttpMethodAttribute_ActionWithMultipleHttpMethodAttributeViaAcceptVerbs_ORsMultipleHttpMethods(string verb)
|
||||
{
|
||||
// Arrange
|
||||
var routeContext = new RouteContext(GetHttpContext(verb));
|
||||
routeContext.RouteData.Values = new Dictionary<string, object>
|
||||
{
|
||||
{ "controller", "HttpMethodAttributeTests_RestOnly" },
|
||||
{ "action", "Patch" }
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await InvokeActionSelector(routeContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Patch", result.Name);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("PUT")]
|
||||
[InlineData("POST")]
|
||||
[InlineData("DELETE")]
|
||||
[InlineData("PATCH")]
|
||||
public async Task HttpMethodAttribute_ActionWithMultipleHttpMethodAttributes_ORsMultipleHttpMethods(string verb)
|
||||
{
|
||||
// Arrange
|
||||
var routeContext = new RouteContext(GetHttpContext(verb));
|
||||
routeContext.RouteData.Values = new Dictionary<string, object>()
|
||||
{
|
||||
{ "controller", "HttpMethodAttributeTests_RestOnly" },
|
||||
{ "action", "Put" }
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await InvokeActionSelector(routeContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Put", result.Name);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("PUT")]
|
||||
public async Task HttpMethodAttribute_ActionDecoratedWithHttpMethodAttribute_OverridesConvention(string verb)
|
||||
{
|
||||
// Arrange
|
||||
// Note no action name is passed, hence should return a null action descriptor.
|
||||
var routeContext = new RouteContext(GetHttpContext(verb));
|
||||
routeContext.RouteData.Values = new Dictionary<string, object>()
|
||||
{
|
||||
{ "controller", "HttpMethodAttributeTests_RestOnly" },
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await InvokeActionSelector(routeContext);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Put")]
|
||||
[InlineData("RPCMethod")]
|
||||
[InlineData("RPCMethodWithHttpGet")]
|
||||
public void NonActionAttribute_ActionNotReachable(string actionName)
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptorProvider = GetActionDescriptorProvider();
|
||||
|
||||
// Act
|
||||
var result = actionDescriptorProvider.GetDescriptors()
|
||||
.Select(x => x as ControllerActionDescriptor)
|
||||
.FirstOrDefault(
|
||||
x => x.ControllerName == "NonAction" &&
|
||||
x.Name == actionName);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET")]
|
||||
[InlineData("PUT")]
|
||||
[InlineData("POST")]
|
||||
[InlineData("DELETE")]
|
||||
[InlineData("PATCH")]
|
||||
public async Task ActionNameAttribute_ActionGetsExposedViaActionName_UnreachableByConvention(string verb)
|
||||
{
|
||||
// Arrange
|
||||
var routeContext = new RouteContext(GetHttpContext(verb));
|
||||
routeContext.RouteData.Values = new Dictionary<string, object>
|
||||
{
|
||||
{ "controller", "ActionName" },
|
||||
{ "action", "RPCMethodWithHttpGet" }
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await InvokeActionSelector(routeContext);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "CustomActionName_Verb")]
|
||||
[InlineData("PUT", "CustomActionName_Verb")]
|
||||
[InlineData("POST", "CustomActionName_Verb")]
|
||||
[InlineData("DELETE", "CustomActionName_Verb")]
|
||||
[InlineData("PATCH", "CustomActionName_Verb")]
|
||||
[InlineData("GET", "CustomActionName_DefaultMethod")]
|
||||
[InlineData("PUT", "CustomActionName_DefaultMethod")]
|
||||
[InlineData("POST", "CustomActionName_DefaultMethod")]
|
||||
[InlineData("DELETE", "CustomActionName_DefaultMethod")]
|
||||
[InlineData("PATCH", "CustomActionName_DefaultMethod")]
|
||||
[InlineData("GET", "CustomActionName_RpcMethod")]
|
||||
[InlineData("PUT", "CustomActionName_RpcMethod")]
|
||||
[InlineData("POST", "CustomActionName_RpcMethod")]
|
||||
[InlineData("DELETE", "CustomActionName_RpcMethod")]
|
||||
[InlineData("PATCH", "CustomActionName_RpcMethod")]
|
||||
public async Task ActionNameAttribute_DifferentActionName_UsesActionNameFromActionNameAttribute(string verb, string actionName)
|
||||
{
|
||||
// Arrange
|
||||
var routeContext = new RouteContext(GetHttpContext(verb));
|
||||
routeContext.RouteData.Values = new Dictionary<string, object>
|
||||
{
|
||||
{ "controller", "ActionName" },
|
||||
{ "action", actionName }
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await InvokeActionSelector(routeContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(actionName, result.Name);
|
||||
}
|
||||
|
||||
private async Task<ActionDescriptor> InvokeActionSelector(RouteContext context)
|
||||
{
|
||||
var actionDescriptorProvider = GetActionDescriptorProvider();
|
||||
var descriptorProvider =
|
||||
new NestedProviderManager<ActionDescriptorProviderContext>(new[] { actionDescriptorProvider });
|
||||
|
||||
var serviceContainer = new ServiceContainer();
|
||||
serviceContainer.AddService(typeof(INestedProviderManager<ActionDescriptorProviderContext>),
|
||||
descriptorProvider);
|
||||
|
||||
var actionCollectionDescriptorProvider = new DefaultActionDescriptorsCollectionProvider(serviceContainer);
|
||||
var decisionTreeProvider = new ActionSelectorDecisionTreeProvider(actionCollectionDescriptorProvider);
|
||||
|
||||
var actionConstraintProvider = new NestedProviderManager<ActionConstraintProviderContext>(
|
||||
new INestedProvider<ActionConstraintProviderContext>[]
|
||||
{
|
||||
new DefaultActionConstraintProvider(serviceContainer),
|
||||
});
|
||||
|
||||
var defaultActionSelector = new DefaultActionSelector(
|
||||
actionCollectionDescriptorProvider,
|
||||
decisionTreeProvider,
|
||||
actionConstraintProvider,
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
return await defaultActionSelector.SelectAsync(context);
|
||||
}
|
||||
|
||||
private ControllerActionDescriptorProvider GetActionDescriptorProvider()
|
||||
{
|
||||
var assemblyProvider = new StaticAssemblyProvider();
|
||||
|
||||
var controllerTypes = typeof(DefaultActionSelectorTests)
|
||||
.GetNestedTypes(BindingFlags.NonPublic)
|
||||
.Select(t => t.GetTypeInfo());
|
||||
|
||||
var modelBuilder = new StaticControllerModelBuilder(controllerTypes.ToArray());
|
||||
|
||||
return new ControllerActionDescriptorProvider(
|
||||
assemblyProvider,
|
||||
modelBuilder,
|
||||
new TestGlobalFilterProvider(),
|
||||
new MockMvcOptionsAccessor());
|
||||
}
|
||||
|
||||
private static HttpContext GetHttpContext(string httpMethod)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Method = httpMethod;
|
||||
return httpContext;
|
||||
}
|
||||
|
||||
private static ActionDescriptor[] GetActions()
|
||||
{
|
||||
return new ActionDescriptor[]
|
||||
|
|
@ -647,9 +844,9 @@ namespace Microsoft.AspNet.Mvc
|
|||
});
|
||||
|
||||
return new DefaultActionSelector(
|
||||
actionProvider.Object,
|
||||
decisionTreeProvider,
|
||||
actionConstraintProvider,
|
||||
actionProvider.Object,
|
||||
decisionTreeProvider,
|
||||
actionConstraintProvider,
|
||||
loggerFactory);
|
||||
}
|
||||
|
||||
|
|
@ -762,5 +959,79 @@ namespace Microsoft.AspNet.Mvc
|
|||
callNext();
|
||||
}
|
||||
}
|
||||
|
||||
private class NonActionController
|
||||
{
|
||||
[NonAction]
|
||||
public void Put()
|
||||
{
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
public void RPCMethod()
|
||||
{
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
[HttpGet]
|
||||
public void RPCMethodWithHttpGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class HttpMethodAttributeTests_DefaultMethodValidationController
|
||||
{
|
||||
public void Index()
|
||||
{
|
||||
}
|
||||
|
||||
// Method with custom attribute.
|
||||
[HttpGet]
|
||||
public void Get()
|
||||
{ }
|
||||
|
||||
// InvalidMethod ( since its private)
|
||||
private void Post()
|
||||
{ }
|
||||
}
|
||||
|
||||
private class ActionNameController
|
||||
{
|
||||
[ActionName("CustomActionName_Verb")]
|
||||
public void Put()
|
||||
{
|
||||
}
|
||||
|
||||
[ActionName("CustomActionName_DefaultMethod")]
|
||||
public void Index()
|
||||
{
|
||||
}
|
||||
|
||||
[ActionName("CustomActionName_RpcMethod")]
|
||||
public void RPCMethodWithHttpGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class HttpMethodAttributeTests_RestOnlyController
|
||||
{
|
||||
[HttpGet]
|
||||
[HttpPut]
|
||||
[HttpPost]
|
||||
[HttpDelete]
|
||||
[HttpPatch]
|
||||
public void Put()
|
||||
{
|
||||
}
|
||||
|
||||
[AcceptVerbs("PUT", "post", "GET", "delete", "pATcH")]
|
||||
public void Patch()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class HttpMethodAttributeTests_DerivedController : HttpMethodAttributeTests_RestOnlyController
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
get
|
||||
{
|
||||
yield return typeof(StaticActionDiscoveryConventions).GetTypeInfo().Assembly;
|
||||
yield return typeof(StaticAssemblyProvider).GetTypeInfo().Assembly;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,22 +5,23 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
namespace Microsoft.AspNet.Mvc.ApplicationModel
|
||||
{
|
||||
/// <summary>
|
||||
/// An implementation of DefaultActionDiscoveryConventions that only allows controllers
|
||||
/// An implementation of StaticControllerModelBuilder that only allows controllers
|
||||
/// from a fixed set of types.
|
||||
/// </summary>
|
||||
public class StaticActionDiscoveryConventions : DefaultActionDiscoveryConventions
|
||||
public class StaticControllerModelBuilder : DefaultControllerModelBuilder
|
||||
{
|
||||
public StaticActionDiscoveryConventions(params TypeInfo[] controllerTypes)
|
||||
public StaticControllerModelBuilder(params TypeInfo[] controllerTypes)
|
||||
: base(new DefaultActionModelBuilder())
|
||||
{
|
||||
ControllerTypes = new List<TypeInfo>(controllerTypes ?? Enumerable.Empty<TypeInfo>());
|
||||
}
|
||||
|
||||
public List<TypeInfo> ControllerTypes { get; private set; }
|
||||
|
||||
public override bool IsController([NotNull]TypeInfo typeInfo)
|
||||
protected override bool IsController([NotNull] TypeInfo typeInfo)
|
||||
{
|
||||
return ControllerTypes.Contains(typeInfo);
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ApplicationModel;
|
||||
using Microsoft.AspNet.Mvc.Filters;
|
||||
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
|
@ -345,8 +346,6 @@ namespace System.Web.Http
|
|||
.SetupGet(fp => fp.Filters)
|
||||
.Returns(new List<IFilter>());
|
||||
|
||||
var conventions = new NamespaceLimitedActionDiscoveryConventions();
|
||||
|
||||
var options = new MvcOptions();
|
||||
|
||||
var setup = new WebApiCompatShimOptionsSetup();
|
||||
|
|
@ -359,7 +358,7 @@ namespace System.Web.Http
|
|||
|
||||
var provider = new ControllerActionDescriptorProvider(
|
||||
assemblyProvider.Object,
|
||||
conventions,
|
||||
new NamespaceLimitedActionDiscoveryConventions(),
|
||||
filterProvider.Object,
|
||||
optionsAccessor.Object);
|
||||
|
||||
|
|
@ -370,9 +369,14 @@ namespace System.Web.Http
|
|||
});
|
||||
}
|
||||
|
||||
private class NamespaceLimitedActionDiscoveryConventions : DefaultActionDiscoveryConventions
|
||||
private class NamespaceLimitedActionDiscoveryConventions : DefaultControllerModelBuilder
|
||||
{
|
||||
public override bool IsController(TypeInfo typeInfo)
|
||||
public NamespaceLimitedActionDiscoveryConventions()
|
||||
: base(new DefaultActionModelBuilder())
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool IsController(TypeInfo typeInfo)
|
||||
{
|
||||
return
|
||||
typeInfo.Namespace == "System.Web.Http.TestControllers" &&
|
||||
|
|
|
|||
Loading…
Reference in New Issue