Fix for #975 - Implementing IActionConstraint and ActionMethodSelectorAttribute
IActionConstraint follows a provider model similar to filters. The attributes that go on actions/controllers can be simple metadata markers, the 'real' constraint is provided by a set of configurable providers. In general the simplest thing to do is to be both an IActionConstraintMetadata and IActionConstraint, and then the default provider will take care of you. IActionConstraint now has stages based on the Order property. Each group of constraints with the same Order will run together on the set of actions. This process is repeated for each value of Order until we run out of actions or run out of constraints. The IActionConstraint interface is beefier than the equivalent in legacy MVC. This is to support cooperative coding between sets of constraints that know about each other. See the changes in the sample, which implement webapi-style overloading.
This commit is contained in:
parent
0d8a7368d9
commit
78a4e78358
|
|
@ -1,4 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using MvcSample.Web.Models;
|
||||
|
||||
namespace MvcSample.Web
|
||||
|
|
@ -11,16 +16,24 @@ namespace MvcSample.Web
|
|||
return Content("Get()");
|
||||
}
|
||||
|
||||
[Overload]
|
||||
public ActionResult Get(int id)
|
||||
{
|
||||
return Content("Get(id)");
|
||||
}
|
||||
|
||||
[Overload]
|
||||
public ActionResult Get(int id, string name)
|
||||
{
|
||||
return Content("Get(id, name)");
|
||||
}
|
||||
|
||||
[Overload]
|
||||
public ActionResult Get(string bleh)
|
||||
{
|
||||
return Content("Get(bleh)");
|
||||
}
|
||||
|
||||
public ActionResult WithUser()
|
||||
{
|
||||
return Content("WithUser()");
|
||||
|
|
@ -47,5 +60,76 @@ namespace MvcSample.Web
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
private class OverloadAttribute : Attribute, IActionConstraint
|
||||
{
|
||||
public int Order { get; } = Int32.MaxValue;
|
||||
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
{
|
||||
var candidates = context.Candidates.Select(a => new
|
||||
{
|
||||
Action = a,
|
||||
Parameters = GetOverloadableParameters(a.Action),
|
||||
});
|
||||
|
||||
var valueProviderFactory = context.RouteContext.HttpContext.RequestServices
|
||||
.GetService<ICompositeValueProviderFactory>();
|
||||
|
||||
var factoryContext = new ValueProviderFactoryContext(
|
||||
context.RouteContext.HttpContext,
|
||||
context.RouteContext.RouteData.Values);
|
||||
var valueProvider = valueProviderFactory.GetValueProvider(factoryContext);
|
||||
|
||||
foreach (var group in candidates.GroupBy(c => c.Parameters.Count).OrderByDescending(g => g.Key))
|
||||
{
|
||||
var foundMatch = false;
|
||||
foreach (var candidate in group)
|
||||
{
|
||||
var allFound = true;
|
||||
foreach (var parameter in candidate.Parameters)
|
||||
{
|
||||
if (!(valueProvider.ContainsPrefixAsync(parameter.ParameterBindingInfo.Prefix).Result))
|
||||
{
|
||||
if (candidate.Action.Action == context.CurrentCandidate.Action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
allFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allFound)
|
||||
{
|
||||
foundMatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundMatch)
|
||||
{
|
||||
return group.Any(c => c.Action.Action == context.CurrentCandidate.Action);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<ParameterDescriptor> GetOverloadableParameters(ActionDescriptor action)
|
||||
{
|
||||
if (action.Parameters == null)
|
||||
{
|
||||
return new List<ParameterDescriptor>();
|
||||
}
|
||||
|
||||
return action.Parameters.Where(
|
||||
p =>
|
||||
p.ParameterBindingInfo != null &&
|
||||
!p.IsOptional &&
|
||||
ValueProviderResult.CanConvertFromString(p.ParameterBindingInfo.ParameterType))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
// 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 Microsoft.AspNet.Routing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Context for <see cref="IActionConstraint"/> execution.
|
||||
/// </summary>
|
||||
public class ActionConstraintContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of <see cref="ActionSelectorCandidate"/>. This includes all actions that are valid for the current
|
||||
/// request, as well as their constraints.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ActionSelectorCandidate> Candidates { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current <see cref="ActionSelectorCandidate"/>.
|
||||
/// </summary>
|
||||
public ActionSelectorCandidate CurrentCandidate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="RouteContext"/>.
|
||||
/// </summary>
|
||||
public RouteContext RouteContext { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an <see cref="IActionConstraintMetadata"/> with or without a corresponding
|
||||
/// <see cref="IActionConstraint"/>.
|
||||
/// </summary>
|
||||
public class ActionConstraintItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ActionConstraintItem"/>.
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="IActionConstraintMetadata"/> instance.</param>
|
||||
public ActionConstraintItem([NotNull] IActionConstraintMetadata metadata)
|
||||
{
|
||||
Metadata = metadata;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IActionConstraint"/> associated with <see cref="Metadata"/>.
|
||||
/// </summary>
|
||||
public IActionConstraint Constraint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IActionConstraintMetadata"/> instance.
|
||||
/// </summary>
|
||||
public IActionConstraintMetadata Metadata { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Context for an action constraint provider.
|
||||
/// </summary>
|
||||
public class ActionConstraintProviderContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ActionConstraintProviderContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="action">The <see cref="ActionDescriptor"/> for which constraints are being created.</param>
|
||||
/// <param name="items">The list of <see cref="ActionConstraintItem"/> objects.</param>
|
||||
public ActionConstraintProviderContext(
|
||||
[NotNull] ActionDescriptor action,
|
||||
[NotNull] IList<ActionConstraintItem> items)
|
||||
{
|
||||
Action = action;
|
||||
Results = items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ActionDescriptor"/> for which constraints are being created.
|
||||
/// </summary>
|
||||
public ActionDescriptor Action { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of <see cref="ActionConstraintItem"/> objects.
|
||||
/// </summary>
|
||||
public IList<ActionConstraintItem> Results { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// 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 Microsoft.AspNet.Routing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for attributes which can implement conditional logic to enable or disable an action
|
||||
/// for a given request. See <see cref="IActionConstraint"/>.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public abstract class ActionMethodSelectorAttribute : Attribute, IActionConstraint
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
{
|
||||
return IsValidForRequest(context.RouteContext, context.CurrentCandidate.Action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the action selection is valid for the specified route context.
|
||||
/// </summary>
|
||||
/// <param name="routeContext">The route context.</param>
|
||||
/// <param name="action">Information about the action.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if the action selection is valid for the specified context;
|
||||
/// otherwise, <see langword="false"/>.
|
||||
/// </returns>
|
||||
public abstract bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// A candidate action for action selection.
|
||||
/// </summary>
|
||||
public class ActionSelectorCandidate
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ActionSelectorCandidate"/>.
|
||||
/// </summary>
|
||||
/// <param name="action">The <see cref="ActionDescriptor"/> representing a candidate for selection.</param>
|
||||
/// <param name="constraints">
|
||||
/// The list of <see cref="IActionConstraint"/> instances associated with <paramref name="action"/>.
|
||||
/// </param>
|
||||
public ActionSelectorCandidate([NotNull] ActionDescriptor action, IReadOnlyList<IActionConstraint> constraints)
|
||||
{
|
||||
Action = action;
|
||||
Constraints = constraints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ActionDescriptor"/> representing a candiate for selection.
|
||||
/// </summary>
|
||||
public ActionDescriptor Action { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of <see cref="IActionConstraint"/> instances associated with <see name="Action"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IActionConstraint> Constraints { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// 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 Microsoft.Framework.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// A default implementation of <see cref="INestedProvider{ActionConstraintProviderContext}"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This provider is able to provide an <see cref="IActionConstraint"/> instance when the
|
||||
/// <see cref="IActionConstraintMetadata"/> implements <see cref="IActionConstraint"/> or
|
||||
/// <see cref="IActionConstraintFactory"/>/
|
||||
/// </remarks>
|
||||
public class DefaultActionConstraintProvider : INestedProvider<ActionConstraintProviderContext>
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DefaultActionConstraintProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="services">The per-request services.</param>
|
||||
public DefaultActionConstraintProvider(IServiceProvider services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Invoke([NotNull] ActionConstraintProviderContext context, [NotNull] Action callNext)
|
||||
{
|
||||
foreach (var item in context.Results)
|
||||
{
|
||||
ProvideConstraint(item);
|
||||
}
|
||||
|
||||
callNext();
|
||||
}
|
||||
|
||||
private void ProvideConstraint(ActionConstraintItem item)
|
||||
{
|
||||
// Don't overwrite anything that was done by a previous provider.
|
||||
if (item.Constraint != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var constraint = item.Metadata as IActionConstraint;
|
||||
if (constraint != null)
|
||||
{
|
||||
item.Constraint = constraint;
|
||||
return;
|
||||
}
|
||||
|
||||
var factory = item.Metadata as IActionConstraintFactory;
|
||||
if (factory != null)
|
||||
{
|
||||
item.Constraint = factory.CreateInstance(_services);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Supports conditional logic to determine whether or not an associated action is valid to be selected
|
||||
/// for the given request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Action constraints have the secondary effect of making an action with a constraint applied a better
|
||||
/// match than one without.
|
||||
///
|
||||
/// Consider two actions, 'A' and 'B' with the same action and controller name. Action 'A' only allows the
|
||||
/// HTTP POST method (via a constraint) and action 'B' has no constraints.
|
||||
///
|
||||
/// If an incoming request is a POST, then 'A' is considered the best match because it both matches and
|
||||
/// has a constraint. If an incoming request uses any other verb, 'A' will not be valid for selection
|
||||
/// due to it's constraint, so 'B' is the best match.
|
||||
///
|
||||
///
|
||||
/// Action constraints are also grouped according to their order value. Any constraints with the same
|
||||
/// group value are considered to be part of the same application policy, and will be executed in the
|
||||
/// same stage.
|
||||
///
|
||||
/// Stages run in ascending order based on the value of <see cref="Order"/>. Given a set of actions which
|
||||
/// are candidates for selection, the next stage to run is the lowest value of <see cref="Order"/> for any
|
||||
/// constraint of any candidate which is greater than the order of the last stage.
|
||||
///
|
||||
/// Once the stage order is identified, each action has all of it's constraints in that stage executed.
|
||||
/// If any constraint does not match, then that action is not a candidate for selection. If any actions
|
||||
/// with constraints in the current state are still candidates, then those are the 'best' actions and this
|
||||
/// process will repeat with the next stage on the set of 'best' actions. If after processing the
|
||||
/// subsequent stages of the 'best' actions no candidates remain, this process will repeat on the set of
|
||||
/// 'other' candidate actions from this stage (those without a constraint).
|
||||
/// </remarks>
|
||||
public interface IActionConstraint : IActionConstraintMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// The constraint order.
|
||||
/// </summary>
|
||||
int Order { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an action is a valid candidate for selection.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="ActionConstraintContext"/>.</param>
|
||||
/// <returns>True if the action is valid for selection, otherwise false.</returns>
|
||||
bool Accept(ActionConstraintContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// A factory for <see cref="IActionConstraint"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="IActionConstraintFactory"/> will be invoked by <see cref="DefaultActionConstraintProvider"/>
|
||||
/// to create constraint instances for an action.
|
||||
///
|
||||
/// Place an attribute implementing this interface on a controller or action to insert an action
|
||||
/// constraint created by a factory.
|
||||
/// </remarks>
|
||||
public interface IActionConstraintFactory : IActionConstraintMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="IActionConstraint"/>.
|
||||
/// </summary>
|
||||
/// <param name="services">The per-request services.</param>
|
||||
/// <returns>An <see cref="IActionConstraint"/>.</returns>
|
||||
IActionConstraint CreateInstance(IServiceProvider services);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// A marker interface that identifies a type as metadata for an <see cref="IActionConstraint"/>.
|
||||
/// </summary>
|
||||
public interface IActionConstraintMetadata
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -23,9 +23,10 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
public Dictionary<string, object> RouteValueDefaults { get; private set; }
|
||||
|
||||
public List<HttpMethodConstraint> MethodConstraints { get; set; }
|
||||
|
||||
public List<IActionConstraint> DynamicConstraints { get; set; }
|
||||
/// <summary>
|
||||
/// The set of constraints for this action. Must all be satisfied for the action to be selected.
|
||||
/// </summary>
|
||||
public List<IActionConstraintMetadata> ActionConstraints { get; set; }
|
||||
|
||||
public List<ParameterDescriptor> Parameters { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// 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 Microsoft.AspNet.Mvc.Routing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
|
|
@ -9,8 +8,13 @@ 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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -45,7 +45,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
return null;
|
||||
}
|
||||
|
||||
var actionInfos = GetActionsForMethodsWithCustomAttributes(methodInfo, controllerTypeInfo);
|
||||
var attributes = GetActionCustomAttributes(methodInfo);
|
||||
var actionInfos = GetActionsForMethodsWithCustomAttributes(attributes, methodInfo, controllerTypeInfo);
|
||||
if (actionInfos.Any())
|
||||
{
|
||||
return actionInfos;
|
||||
|
|
@ -58,6 +59,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
new ActionInfo()
|
||||
{
|
||||
ActionName = methodInfo.Name,
|
||||
Attributes = attributes.Attributes,
|
||||
RequireActionNameMatch = true,
|
||||
}
|
||||
};
|
||||
|
|
@ -89,21 +91,16 @@ namespace Microsoft.AspNet.Mvc
|
|||
method.GetBaseDefinition().DeclaringType != typeof(object);
|
||||
}
|
||||
|
||||
private bool HasCustomAttributes(MethodInfo methodInfo)
|
||||
{
|
||||
var actionAttributes = GetActionCustomAttributes(methodInfo);
|
||||
return actionAttributes.Any();
|
||||
}
|
||||
|
||||
private ActionAttributes GetActionCustomAttributes(MethodInfo methodInfo)
|
||||
{
|
||||
var attributes = methodInfo.GetCustomAttributes();
|
||||
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,
|
||||
|
|
@ -111,16 +108,16 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
|
||||
private IEnumerable<ActionInfo> GetActionsForMethodsWithCustomAttributes(
|
||||
ActionAttributes actionAttributes,
|
||||
MethodInfo methodInfo,
|
||||
TypeInfo controller)
|
||||
{
|
||||
var hasControllerAttributeRoutes = HasValidControllerRouteTemplates(controller);
|
||||
var actionAttributes = GetActionCustomAttributes(methodInfo);
|
||||
|
||||
// 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.Any() || hasControllerAttributeRoutes)
|
||||
if (actionAttributes.HasSpecialAttribute() || hasControllerAttributeRoutes)
|
||||
{
|
||||
var actionNameAttribute = actionAttributes.ActionNameAttribute;
|
||||
var actionName = actionNameAttribute != null ? actionNameAttribute.Name : methodInfo.Name;
|
||||
|
|
@ -178,6 +175,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
HttpMethods = httpMethods,
|
||||
ActionName = actionName,
|
||||
Attributes = actionAttributes.Attributes,
|
||||
RequireActionNameMatch = true,
|
||||
};
|
||||
}
|
||||
|
|
@ -194,6 +192,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
actions.Add(new ActionInfo
|
||||
{
|
||||
Attributes = actionAttributes.Attributes,
|
||||
ActionName = actionName,
|
||||
HttpMethods = null,
|
||||
RequireActionNameMatch = true,
|
||||
|
|
@ -203,8 +202,14 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
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,
|
||||
|
|
@ -229,10 +234,13 @@ namespace Microsoft.AspNet.Mvc
|
|||
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 Any()
|
||||
public bool HasSpecialAttribute()
|
||||
{
|
||||
return ActionNameAttribute != null ||
|
||||
HttpMethodProviderAttributes.Any() ||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using Microsoft.AspNet.Mvc.Core;
|
|||
using Microsoft.AspNet.Mvc.Logging;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
|
|
@ -17,15 +18,18 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
private readonly IActionDescriptorsCollectionProvider _actionDescriptorsCollectionProvider;
|
||||
private readonly IActionSelectorDecisionTreeProvider _decisionTreeProvider;
|
||||
private readonly INestedProviderManager<ActionConstraintProviderContext> _actionConstraintProvider;
|
||||
private ILogger _logger;
|
||||
|
||||
public DefaultActionSelector(
|
||||
[NotNull] IActionDescriptorsCollectionProvider actionDescriptorsCollectionProvider,
|
||||
[NotNull] IActionSelectorDecisionTreeProvider decisionTreeProvider,
|
||||
[NotNull] INestedProviderManager<ActionConstraintProviderContext> actionConstraintProvider,
|
||||
[NotNull] ILoggerFactory loggerFactory)
|
||||
{
|
||||
_actionDescriptorsCollectionProvider = actionDescriptorsCollectionProvider;
|
||||
_decisionTreeProvider = decisionTreeProvider;
|
||||
_actionConstraintProvider = actionConstraintProvider;
|
||||
_logger = loggerFactory.Create<DefaultActionSelector>();
|
||||
}
|
||||
|
||||
|
|
@ -36,45 +40,36 @@ namespace Microsoft.AspNet.Mvc
|
|||
var tree = _decisionTreeProvider.DecisionTree;
|
||||
var matchingRouteConstraints = tree.Select(context.RouteData.Values);
|
||||
|
||||
var matchingRouteAndMethodConstraints =
|
||||
matchingRouteConstraints.Where(ad =>
|
||||
MatchMethodConstraints(ad, context)).ToList();
|
||||
|
||||
var matchingRouteAndMethodAndDynamicConstraints =
|
||||
matchingRouteAndMethodConstraints.Where(ad =>
|
||||
MatchDynamicConstraints(ad, context)).ToList();
|
||||
|
||||
var matching = matchingRouteAndMethodAndDynamicConstraints;
|
||||
|
||||
var matchesWithConstraints = new List<ActionDescriptor>();
|
||||
foreach (var match in matching)
|
||||
var candidates = new List<ActionSelectorCandidate>();
|
||||
foreach (var action in matchingRouteConstraints)
|
||||
{
|
||||
if (match.DynamicConstraints != null && match.DynamicConstraints.Any() ||
|
||||
match.MethodConstraints != null && match.MethodConstraints.Any())
|
||||
var constraints = GetConstraints(action);
|
||||
candidates.Add(new ActionSelectorCandidate(action, constraints));
|
||||
}
|
||||
|
||||
var matchingActionConstraints =
|
||||
EvaluateActionConstraints(context, candidates, startingOrder: null);
|
||||
|
||||
List<ActionDescriptor> matchingActions = null;
|
||||
if (matchingActionConstraints != null)
|
||||
{
|
||||
matchingActions = new List<ActionDescriptor>(matchingActionConstraints.Count);
|
||||
foreach (var candidate in matchingActionConstraints)
|
||||
{
|
||||
matchesWithConstraints.Add(match);
|
||||
matchingActions.Add(candidate.Action);
|
||||
}
|
||||
}
|
||||
|
||||
// If any action that's applicable has constraints, this is considered better than
|
||||
// an action without.
|
||||
if (matchesWithConstraints.Any())
|
||||
{
|
||||
matching = matchesWithConstraints;
|
||||
}
|
||||
var finalMatches = SelectBestActions(matchingActions);
|
||||
|
||||
var finalMatches = SelectBestActions(matching);
|
||||
|
||||
if (finalMatches.Count == 0)
|
||||
if (finalMatches == null || finalMatches.Count == 0)
|
||||
{
|
||||
if (_logger.IsEnabled(TraceType.Information))
|
||||
{
|
||||
_logger.WriteValues(new DefaultActionSelectorSelectAsyncValues()
|
||||
{
|
||||
ActionsMatchingRouteConstraints = matchingRouteConstraints,
|
||||
ActionsMatchingRouteAndMethodConstraints = matchingRouteAndMethodConstraints,
|
||||
ActionsMatchingRouteAndMethodAndDynamicConstraints =
|
||||
matchingRouteAndMethodAndDynamicConstraints,
|
||||
ActionsMatchingActionConstraints = matchingActions,
|
||||
FinalMatches = finalMatches,
|
||||
});
|
||||
}
|
||||
|
|
@ -90,9 +85,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
_logger.WriteValues(new DefaultActionSelectorSelectAsyncValues()
|
||||
{
|
||||
ActionsMatchingRouteConstraints = matchingRouteConstraints,
|
||||
ActionsMatchingRouteAndMethodConstraints = matchingRouteAndMethodConstraints,
|
||||
ActionsMatchingRouteAndMethodAndDynamicConstraints =
|
||||
matchingRouteAndMethodAndDynamicConstraints,
|
||||
ActionsMatchingActionConstraints = matchingActions,
|
||||
FinalMatches = finalMatches,
|
||||
SelectedAction = selectedAction
|
||||
});
|
||||
|
|
@ -107,9 +100,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
_logger.WriteValues(new DefaultActionSelectorSelectAsyncValues()
|
||||
{
|
||||
ActionsMatchingRouteConstraints = matchingRouteConstraints,
|
||||
ActionsMatchingRouteAndMethodConstraints = matchingRouteAndMethodConstraints,
|
||||
ActionsMatchingRouteAndMethodAndDynamicConstraints =
|
||||
matchingRouteAndMethodAndDynamicConstraints,
|
||||
ActionsMatchingActionConstraints = matchingActions,
|
||||
FinalMatches = finalMatches,
|
||||
});
|
||||
}
|
||||
|
|
@ -137,16 +128,96 @@ namespace Microsoft.AspNet.Mvc
|
|||
return actions;
|
||||
}
|
||||
|
||||
private bool MatchMethodConstraints(ActionDescriptor descriptor, RouteContext context)
|
||||
private IReadOnlyList<ActionSelectorCandidate> EvaluateActionConstraints(
|
||||
RouteContext context,
|
||||
IReadOnlyList<ActionSelectorCandidate> candidates,
|
||||
int? startingOrder)
|
||||
{
|
||||
return descriptor.MethodConstraints == null ||
|
||||
descriptor.MethodConstraints.All(c => c.Accept(context));
|
||||
}
|
||||
// Find the next group of constraints to process. This will be the lowest value of
|
||||
// order that is higher than startingOrder.
|
||||
int? order = null;
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (candidate.Constraints != null)
|
||||
{
|
||||
foreach (var constraint in candidate.Constraints)
|
||||
{
|
||||
if ((startingOrder == null || constraint.Order > startingOrder) &&
|
||||
(order == null || constraint.Order < order))
|
||||
{
|
||||
order = constraint.Order;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool MatchDynamicConstraints(ActionDescriptor descriptor, RouteContext context)
|
||||
{
|
||||
return descriptor.DynamicConstraints == null ||
|
||||
descriptor.DynamicConstraints.All(c => c.Accept(context));
|
||||
// If we don't find a 'next' then there's nothing left to do.
|
||||
if (order == null)
|
||||
{
|
||||
return candidates;
|
||||
}
|
||||
|
||||
// Since we have a constraint to process, bisect the set of actions into those with and without a
|
||||
// constraint for the 'current order'.
|
||||
var actionsWithConstraint = new List<ActionSelectorCandidate>();
|
||||
var actionsWithoutConstraint = new List<ActionSelectorCandidate>();
|
||||
|
||||
var constraintContext = new ActionConstraintContext();
|
||||
constraintContext.Candidates = candidates;
|
||||
constraintContext.RouteContext = context;
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
var isMatch = true;
|
||||
var foundMatchingConstraint = false;
|
||||
|
||||
if (candidate.Constraints != null)
|
||||
{
|
||||
constraintContext.CurrentCandidate = candidate;
|
||||
foreach (var constraint in candidate.Constraints)
|
||||
{
|
||||
if (constraint.Order == order)
|
||||
{
|
||||
foundMatchingConstraint = true;
|
||||
|
||||
if (!constraint.Accept(constraintContext))
|
||||
{
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isMatch && foundMatchingConstraint)
|
||||
{
|
||||
actionsWithConstraint.Add(candidate);
|
||||
}
|
||||
else if (isMatch)
|
||||
{
|
||||
actionsWithoutConstraint.Add(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have matches with constraints, those are 'better' so try to keep processing those
|
||||
if (actionsWithConstraint.Count > 0)
|
||||
{
|
||||
var matches = EvaluateActionConstraints(context, actionsWithConstraint, order);
|
||||
if (matches?.Count > 0)
|
||||
{
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
|
||||
// If the set of matches with constraints can't work, then process the set without constraints.
|
||||
if (actionsWithoutConstraint.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return EvaluateActionConstraints(context, actionsWithoutConstraint, order);
|
||||
}
|
||||
}
|
||||
|
||||
// This method attempts to ensure that the route that's about to generate a link will generate a link
|
||||
|
|
@ -184,5 +255,24 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
return descriptors.Items;
|
||||
}
|
||||
|
||||
private IReadOnlyList<IActionConstraint> GetConstraints(ActionDescriptor action)
|
||||
{
|
||||
if (action.ActionConstraints == null || action.ActionConstraints.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var items = action.ActionConstraints.Select(c => new ActionConstraintItem(c)).ToList();
|
||||
var context = new ActionConstraintProviderContext(action, items);
|
||||
|
||||
_actionConstraintProvider.Invoke(context);
|
||||
|
||||
return
|
||||
context.Results
|
||||
.Where(item => item.Constraint != null)
|
||||
.Select(item => item.Constraint)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,9 +119,9 @@ namespace Microsoft.AspNet.Mvc.Description
|
|||
|
||||
private IEnumerable<string> GetHttpMethods(ReflectedActionDescriptor action)
|
||||
{
|
||||
if (action.MethodConstraints != null && action.MethodConstraints.Count > 0)
|
||||
if (action.ActionConstraints != null && action.ActionConstraints.Count > 0)
|
||||
{
|
||||
return action.MethodConstraints.SelectMany(c => c.HttpMethods);
|
||||
return action.ActionConstraints.OfType<HttpMethodConstraint>().SelectMany(c => c.HttpMethods);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,22 +5,18 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Routing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class HttpMethodConstraint : IActionConstraint
|
||||
{
|
||||
public static readonly int HttpMethodConstraintOrder = 100;
|
||||
|
||||
private readonly IReadOnlyList<string> _methods;
|
||||
|
||||
// Empty collection means any method will be accepted.
|
||||
public HttpMethodConstraint(IEnumerable<string> httpMethods)
|
||||
public HttpMethodConstraint([NotNull] IEnumerable<string> httpMethods)
|
||||
{
|
||||
if (httpMethods == null)
|
||||
{
|
||||
throw new ArgumentNullException("httpMethods");
|
||||
}
|
||||
|
||||
var methods = new List<string>();
|
||||
|
||||
foreach (var method in httpMethods)
|
||||
|
|
@ -44,19 +40,19 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
}
|
||||
|
||||
public bool Accept([NotNull] RouteContext context)
|
||||
public int Order
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException("context");
|
||||
}
|
||||
get { return HttpMethodConstraintOrder; }
|
||||
}
|
||||
|
||||
public bool Accept([NotNull] ActionConstraintContext context)
|
||||
{
|
||||
if (_methods.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var request = context.HttpContext.Request;
|
||||
var request = context.RouteContext.HttpContext.Request;
|
||||
|
||||
return (HttpMethods.Any(m => m.Equals(request.Method, StringComparison.Ordinal)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +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.Routing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public interface IActionConstraint
|
||||
{
|
||||
bool Accept([NotNull] RouteContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -28,17 +28,13 @@ namespace Microsoft.AspNet.Mvc.Logging
|
|||
public IReadOnlyList<ActionDescriptor> ActionsMatchingRouteConstraints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of actions that matched all their route and method constraints, if any.
|
||||
/// The list of actions that matched all their route constraints, and action constraints, if any.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ActionDescriptor> ActionsMatchingRouteAndMethodConstraints { get; set; }
|
||||
public IReadOnlyList<ActionDescriptor> ActionsMatchingActionConstraints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of actions that matched all their route, method, and dynamic constraints, if any.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ActionDescriptor> ActionsMatchingRouteAndMethodAndDynamicConstraints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of actions that are the best matches. These match all constraints.
|
||||
/// The list of actions that are the best matches. These match all constraints and any additional criteria
|
||||
/// for disambiguation.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ActionDescriptor> FinalMatches { get; set; }
|
||||
|
||||
|
|
@ -59,13 +55,10 @@ namespace Microsoft.AspNet.Mvc.Logging
|
|||
builder.Append("\tActions matching route constraints: ");
|
||||
StringBuilderHelpers.Append(builder, ActionsMatchingRouteConstraints, Formatter);
|
||||
builder.AppendLine();
|
||||
builder.Append("\tActions matching route and method constraints: ");
|
||||
StringBuilderHelpers.Append(builder, ActionsMatchingRouteAndMethodConstraints, Formatter);
|
||||
builder.Append("\tActions matching action constraints: ");
|
||||
StringBuilderHelpers.Append(builder, ActionsMatchingActionConstraints, Formatter);
|
||||
builder.AppendLine();
|
||||
builder.Append("\tActions matching route, method, and dynamic constraints: ");
|
||||
StringBuilderHelpers.Append(builder, ActionsMatchingRouteAndMethodAndDynamicConstraints, Formatter);
|
||||
builder.AppendLine();
|
||||
builder.Append("\tBest Matches: ");
|
||||
builder.Append("\tFinal Matches: ");
|
||||
StringBuilderHelpers.Append(builder, FinalMatches, Formatter);
|
||||
builder.AppendLine();
|
||||
builder.Append("\tSelected action: ");
|
||||
|
|
|
|||
|
|
@ -72,11 +72,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
foreach (var controllerType in controllerTypes)
|
||||
{
|
||||
var controllerModel = new ReflectedControllerModel(controllerType)
|
||||
{
|
||||
Application = applicationModel,
|
||||
};
|
||||
|
||||
var controllerModel = CreateControllerModel(applicationModel, controllerType);
|
||||
applicationModel.Controllers.Add(controllerModel);
|
||||
|
||||
foreach (var methodInfo in controllerType.AsType().GetMethods())
|
||||
|
|
@ -89,30 +85,14 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
foreach (var actionInfo in actionInfos)
|
||||
{
|
||||
var actionModel = new ReflectedActionModel(methodInfo)
|
||||
{
|
||||
ActionName = actionInfo.ActionName,
|
||||
Controller = controllerModel,
|
||||
IsActionNameMatchRequired = actionInfo.RequireActionNameMatch,
|
||||
};
|
||||
|
||||
actionModel.HttpMethods.AddRange(actionInfo.HttpMethods ?? Enumerable.Empty<string>());
|
||||
|
||||
if (actionInfo.AttributeRoute != null)
|
||||
{
|
||||
actionModel.AttributeRouteModel = new ReflectedAttributeRouteModel(
|
||||
actionInfo.AttributeRoute);
|
||||
}
|
||||
|
||||
foreach (var parameter in methodInfo.GetParameters())
|
||||
{
|
||||
actionModel.Parameters.Add(new ReflectedParameterModel(parameter)
|
||||
{
|
||||
Action = actionModel,
|
||||
});
|
||||
}
|
||||
|
||||
var actionModel = CreateActionModel(controllerModel, methodInfo, actionInfo);
|
||||
controllerModel.Actions.Add(actionModel);
|
||||
|
||||
foreach (var parameterInfo in methodInfo.GetParameters())
|
||||
{
|
||||
var parameterModel = CreateParameterModel(actionModel, parameterInfo);
|
||||
actionModel.Parameters.Add(parameterModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -120,6 +100,109 @@ namespace Microsoft.AspNet.Mvc
|
|||
return applicationModel;
|
||||
}
|
||||
|
||||
private ReflectedControllerModel CreateControllerModel(
|
||||
ReflectedApplicationModel applicationModel,
|
||||
TypeInfo controllerType)
|
||||
{
|
||||
var controllerModel = new ReflectedControllerModel(controllerType)
|
||||
{
|
||||
Application = applicationModel,
|
||||
};
|
||||
|
||||
controllerModel.ControllerName =
|
||||
controllerType.Name.EndsWith("Controller", StringComparison.Ordinal) ?
|
||||
controllerType.Name.Substring(0, controllerType.Name.Length - "Controller".Length) :
|
||||
controllerType.Name;
|
||||
|
||||
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
|
||||
// is needed to so that the result of ToList() is List<object>
|
||||
var attributes = controllerType.GetCustomAttributes(inherit: true).ToList();
|
||||
controllerModel.Attributes.AddRange(attributes);
|
||||
|
||||
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 ReflectedAttributeRouteModel(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;
|
||||
}
|
||||
|
||||
private ReflectedActionModel CreateActionModel(
|
||||
ReflectedControllerModel controllerModel,
|
||||
MethodInfo methodInfo,
|
||||
ActionInfo actionInfo)
|
||||
{
|
||||
var actionModel = new ReflectedActionModel(methodInfo)
|
||||
{
|
||||
ActionName = actionInfo.ActionName,
|
||||
Controller = controllerModel,
|
||||
IsActionNameMatchRequired = actionInfo.RequireActionNameMatch,
|
||||
};
|
||||
|
||||
var attributes = actionInfo.Attributes;
|
||||
|
||||
actionModel.Attributes.AddRange(attributes);
|
||||
|
||||
actionModel.ActionConstraints.AddRange(attributes.OfType<IActionConstraintMetadata>());
|
||||
actionModel.Filters.AddRange(attributes.OfType<IFilter>());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
actionModel.HttpMethods.AddRange(actionInfo.HttpMethods ?? Enumerable.Empty<string>());
|
||||
|
||||
if (actionInfo.AttributeRoute != null)
|
||||
{
|
||||
actionModel.AttributeRouteModel = new ReflectedAttributeRouteModel(
|
||||
actionInfo.AttributeRoute);
|
||||
}
|
||||
|
||||
return actionModel;
|
||||
}
|
||||
|
||||
private ReflectedParameterModel CreateParameterModel(
|
||||
ReflectedActionModel actionModel,
|
||||
ParameterInfo parameterInfo)
|
||||
{
|
||||
var parameterModel = new ReflectedParameterModel(parameterInfo)
|
||||
{
|
||||
Action = actionModel,
|
||||
};
|
||||
|
||||
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
|
||||
// is needed to so that the result of ToList() is List<object>
|
||||
var attributes = parameterInfo.GetCustomAttributes(inherit: true).OfType<object>().ToList();
|
||||
parameterModel.Attributes.AddRange(attributes);
|
||||
|
||||
parameterModel.ParameterName = parameterInfo.Name;
|
||||
parameterModel.IsOptional = parameterInfo.HasDefaultValue;
|
||||
|
||||
return parameterModel;
|
||||
}
|
||||
|
||||
public void ApplyConventions(ReflectedApplicationModel model)
|
||||
{
|
||||
// Conventions are applied from the outside-in to allow for scenarios where an action overrides
|
||||
|
|
@ -176,7 +259,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
}
|
||||
|
||||
public List<ReflectedActionDescriptor> Build(ReflectedApplicationModel model)
|
||||
public List<ReflectedActionDescriptor> Build(ReflectedApplicationModel application)
|
||||
{
|
||||
var actions = new List<ReflectedActionDescriptor>();
|
||||
|
||||
|
|
@ -188,7 +271,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
var routeTemplateErrors = new List<string>();
|
||||
var attributeRoutingConfigurationErrors = new Dictionary<MethodInfo, string>();
|
||||
|
||||
foreach (var controller in model.Controllers)
|
||||
foreach (var controller in application.Controllers)
|
||||
{
|
||||
var controllerDescriptor = new ControllerDescriptor()
|
||||
{
|
||||
|
|
@ -203,13 +286,14 @@ namespace Microsoft.AspNet.Mvc
|
|||
// instance.
|
||||
// Actions with multiple [Http*] attributes or other (IRouteTemplateProvider implementations
|
||||
// have already been identified as different actions during action discovery.
|
||||
var actionDescriptors = CreateActionDescriptors(action, controller, controllerDescriptor);
|
||||
var actionDescriptors = CreateActionDescriptors(application, controller, action);
|
||||
|
||||
foreach (var actionDescriptor in actionDescriptors)
|
||||
{
|
||||
actionDescriptor.ControllerDescriptor = controllerDescriptor;
|
||||
|
||||
AddApiExplorerInfo(actionDescriptor, action, controller);
|
||||
AddActionFilters(actionDescriptor, action.Filters, controller.Filters, model.Filters);
|
||||
AddActionConstraints(actionDescriptor, action, controller);
|
||||
AddRouteConstraints(actionDescriptor, controller, action);
|
||||
AddControllerRouteConstraints(
|
||||
actionDescriptor,
|
||||
controller.RouteConstraints,
|
||||
|
|
@ -324,37 +408,71 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
|
||||
private static IList<ReflectedActionDescriptor> CreateActionDescriptors(
|
||||
ReflectedActionModel action,
|
||||
ReflectedApplicationModel application,
|
||||
ReflectedControllerModel controller,
|
||||
ControllerDescriptor controllerDescriptor)
|
||||
ReflectedActionModel action)
|
||||
{
|
||||
var actionDescriptors = new List<ReflectedActionDescriptor>();
|
||||
|
||||
// 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
|
||||
// attribute identical attribute routes.
|
||||
if (controller.AttributeRoutes != null &&
|
||||
controller.AttributeRoutes.Count > 0 &&
|
||||
(action.AttributeRouteModel == null ||
|
||||
!action.AttributeRouteModel.IsAbsoluteTemplate))
|
||||
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,
|
||||
controllerDescriptor);
|
||||
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
|
||||
{
|
||||
actionDescriptors.Add(CreateActionDescriptor(
|
||||
// No attribute routes on the controller
|
||||
var actionDescriptor = CreateActionDescriptor(
|
||||
action,
|
||||
controllerAttributeRoute: null,
|
||||
controllerDescriptor: controllerDescriptor));
|
||||
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;
|
||||
|
|
@ -362,32 +480,13 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
private static ReflectedActionDescriptor CreateActionDescriptor(
|
||||
ReflectedActionModel action,
|
||||
ReflectedAttributeRouteModel controllerAttributeRoute,
|
||||
ControllerDescriptor controllerDescriptor)
|
||||
ReflectedAttributeRouteModel controllerAttributeRoute)
|
||||
{
|
||||
var parameterDescriptors = new List<ParameterDescriptor>();
|
||||
foreach (var parameter in action.Parameters)
|
||||
{
|
||||
var isFromBody = parameter.Attributes.OfType<FromBodyAttribute>().Any();
|
||||
var paramDescriptor = new ParameterDescriptor()
|
||||
{
|
||||
Name = parameter.ParameterName,
|
||||
IsOptional = parameter.IsOptional
|
||||
};
|
||||
|
||||
if (isFromBody)
|
||||
{
|
||||
paramDescriptor.BodyParameterInfo = new BodyParameterInfo(
|
||||
parameter.ParameterInfo.ParameterType);
|
||||
}
|
||||
else
|
||||
{
|
||||
paramDescriptor.ParameterBindingInfo = new ParameterBindingInfo(
|
||||
parameter.ParameterName,
|
||||
parameter.ParameterInfo.ParameterType);
|
||||
}
|
||||
|
||||
parameterDescriptors.Add(paramDescriptor);
|
||||
var parameterDescriptor = CreateParameterDescriptor(parameter);
|
||||
parameterDescriptors.Add(parameterDescriptor);
|
||||
}
|
||||
|
||||
var attributeRouteInfo = CreateAttributeRouteInfo(
|
||||
|
|
@ -397,7 +496,6 @@ namespace Microsoft.AspNet.Mvc
|
|||
var actionDescriptor = new ReflectedActionDescriptor()
|
||||
{
|
||||
Name = action.ActionName,
|
||||
ControllerDescriptor = controllerDescriptor,
|
||||
MethodInfo = action.ActionMethod,
|
||||
Parameters = parameterDescriptors,
|
||||
RouteConstraints = new List<RouteDataActionConstraint>(),
|
||||
|
|
@ -412,6 +510,30 @@ namespace Microsoft.AspNet.Mvc
|
|||
return actionDescriptor;
|
||||
}
|
||||
|
||||
private static ParameterDescriptor CreateParameterDescriptor(ReflectedParameterModel parameter)
|
||||
{
|
||||
var parameterDescriptor = new ParameterDescriptor()
|
||||
{
|
||||
Name = parameter.ParameterName,
|
||||
IsOptional = parameter.IsOptional
|
||||
};
|
||||
|
||||
var isFromBody = parameter.Attributes.OfType<FromBodyAttribute>().Any();
|
||||
if (isFromBody)
|
||||
{
|
||||
parameterDescriptor.BodyParameterInfo = new BodyParameterInfo(
|
||||
parameter.ParameterInfo.ParameterType);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameterDescriptor.ParameterBindingInfo = new ParameterBindingInfo(
|
||||
parameter.ParameterName,
|
||||
parameter.ParameterInfo.ParameterType);
|
||||
}
|
||||
|
||||
return parameterDescriptor;
|
||||
}
|
||||
|
||||
private static void AddApiExplorerInfo(
|
||||
ReflectedActionDescriptor actionDescriptor,
|
||||
ReflectedActionModel action,
|
||||
|
|
@ -469,17 +591,37 @@ namespace Microsoft.AspNet.Mvc
|
|||
private static void AddActionConstraints(
|
||||
ReflectedActionDescriptor actionDescriptor,
|
||||
ReflectedActionModel action,
|
||||
ReflectedControllerModel controller)
|
||||
IEnumerable<IActionConstraintMetadata> controllerConstraints)
|
||||
{
|
||||
var constraints = new List<IActionConstraintMetadata>();
|
||||
|
||||
var httpMethods = action.HttpMethods;
|
||||
if (httpMethods != null && httpMethods.Count > 0)
|
||||
{
|
||||
actionDescriptor.MethodConstraints = new List<HttpMethodConstraint>()
|
||||
{
|
||||
new HttpMethodConstraint(httpMethods)
|
||||
};
|
||||
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 void AddRouteConstraints(
|
||||
ReflectedActionDescriptor actionDescriptor,
|
||||
ReflectedControllerModel controller,
|
||||
ReflectedActionModel action)
|
||||
{
|
||||
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
|
||||
"controller",
|
||||
controller.ControllerName));
|
||||
|
|
|
|||
|
|
@ -15,36 +15,15 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
|||
{
|
||||
ActionMethod = actionMethod;
|
||||
|
||||
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
|
||||
// is needed to so that the result of ToList() is List<object>
|
||||
Attributes = actionMethod.GetCustomAttributes(inherit: true).OfType<object>().ToList();
|
||||
|
||||
Filters = Attributes
|
||||
.OfType<IFilter>()
|
||||
.ToList();
|
||||
|
||||
var routeTemplateAttribute = Attributes.OfType<IRouteTemplateProvider>().FirstOrDefault();
|
||||
if (routeTemplateAttribute != null)
|
||||
{
|
||||
AttributeRouteModel = new ReflectedAttributeRouteModel(routeTemplateAttribute);
|
||||
}
|
||||
|
||||
var apiExplorerNameAttribute = Attributes.OfType<IApiDescriptionGroupNameProvider>().FirstOrDefault();
|
||||
if (apiExplorerNameAttribute != null)
|
||||
{
|
||||
ApiExplorerGroupName = apiExplorerNameAttribute.GroupName;
|
||||
}
|
||||
|
||||
var apiExplorerVisibilityAttribute = Attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault();
|
||||
if (apiExplorerVisibilityAttribute != null)
|
||||
{
|
||||
ApiExplorerIsVisible = !apiExplorerVisibilityAttribute.IgnoreApi;
|
||||
}
|
||||
|
||||
Attributes = new List<object>();
|
||||
ActionConstraints = new List<IActionConstraintMetadata>();
|
||||
Filters = new List<IFilter>();
|
||||
HttpMethods = new List<string>();
|
||||
Parameters = new List<ReflectedParameterModel>();
|
||||
}
|
||||
|
||||
public List<IActionConstraintMetadata> ActionConstraints { get; private set; }
|
||||
|
||||
public MethodInfo ActionMethod { get; private set; }
|
||||
|
||||
public string ActionName { get; set; }
|
||||
|
|
|
|||
|
|
@ -19,11 +19,14 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
|||
|
||||
public ReflectedAttributeRouteModel([NotNull] IRouteTemplateProvider templateProvider)
|
||||
{
|
||||
Attribute = templateProvider;
|
||||
Template = templateProvider.Template;
|
||||
Order = templateProvider.Order;
|
||||
Name = templateProvider.Name;
|
||||
}
|
||||
|
||||
public IRouteTemplateProvider Attribute { get; private set; }
|
||||
|
||||
public string Template { get; set; }
|
||||
|
||||
public int? Order { get; set; }
|
||||
|
|
|
|||
|
|
@ -3,10 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||
{
|
||||
|
|
@ -17,38 +14,15 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
|||
ControllerType = controllerType;
|
||||
|
||||
Actions = new List<ReflectedActionModel>();
|
||||
|
||||
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
|
||||
// is needed to so that the result of ToList() is List<object>
|
||||
Attributes = ControllerType.GetCustomAttributes(inherit: true).OfType<object>().ToList();
|
||||
|
||||
Filters = Attributes
|
||||
.OfType<IFilter>()
|
||||
.ToList();
|
||||
|
||||
RouteConstraints = Attributes.OfType<RouteConstraintAttribute>().ToList();
|
||||
|
||||
AttributeRoutes = Attributes.OfType<IRouteTemplateProvider>()
|
||||
.Select(rtp => new ReflectedAttributeRouteModel(rtp))
|
||||
.ToList();
|
||||
|
||||
var apiExplorerNameAttribute = Attributes.OfType<IApiDescriptionGroupNameProvider>().FirstOrDefault();
|
||||
if (apiExplorerNameAttribute != null)
|
||||
{
|
||||
ApiExplorerGroupName = apiExplorerNameAttribute.GroupName;
|
||||
}
|
||||
|
||||
var apiExplorerVisibilityAttribute = Attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault();
|
||||
if (apiExplorerVisibilityAttribute != null)
|
||||
{
|
||||
ApiExplorerIsVisible = !apiExplorerVisibilityAttribute.IgnoreApi;
|
||||
}
|
||||
|
||||
ControllerName = controllerType.Name.EndsWith("Controller", StringComparison.Ordinal)
|
||||
? controllerType.Name.Substring(0, controllerType.Name.Length - "Controller".Length)
|
||||
: controllerType.Name;
|
||||
Attributes = new List<object>();
|
||||
AttributeRoutes = new List<ReflectedAttributeRouteModel>();
|
||||
ActionConstraints = new List<IActionConstraintMetadata>();
|
||||
Filters = new List<IFilter>();
|
||||
RouteConstraints = new List<RouteConstraintAttribute>();
|
||||
}
|
||||
|
||||
public List<IActionConstraintMetadata> ActionConstraints { get; private set; }
|
||||
|
||||
public List<ReflectedActionModel> Actions { get; private set; }
|
||||
|
||||
public ReflectedApplicationModel Application { get; set; }
|
||||
|
|
@ -66,14 +40,14 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
|||
public List<ReflectedAttributeRouteModel> AttributeRoutes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If <c>true</c>, <see cref="ApiDescription"/> objects will be created for actions defined by this
|
||||
/// controller.
|
||||
/// If <c>true</c>, <see cref="Description.ApiDescription"/> objects will be created for actions defined by
|
||||
/// this controller.
|
||||
/// </summary>
|
||||
public bool? ApiExplorerIsVisible { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value for <see cref="ApiDescription.GroupName"/> of <see cref="ApiDescription"/> objects created
|
||||
/// for actions defined by this controller.
|
||||
/// The value for <see cref="Description.ApiDescription.GroupName"/> of
|
||||
/// <see cref="Description.ApiDescription"/> objects created for actions defined by this controller.
|
||||
/// </summary>
|
||||
public string ApiExplorerGroupName { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
||||
|
|
@ -13,12 +12,7 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
|
|||
{
|
||||
ParameterInfo = parameterInfo;
|
||||
|
||||
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
|
||||
// is needed to so that the result of ToList() is List<object>
|
||||
Attributes = parameterInfo.GetCustomAttributes(inherit: true).OfType<object>().ToList();
|
||||
|
||||
ParameterName = parameterInfo.Name;
|
||||
IsOptional = ParameterInfo.HasDefaultValue;
|
||||
Attributes = new List<object>();
|
||||
}
|
||||
|
||||
public ReflectedActionModel Action { get; set; }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ using Microsoft.AspNet.Routing;
|
|||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class RouteDataActionConstraint : IActionConstraint
|
||||
public class RouteDataActionConstraint
|
||||
{
|
||||
private IEqualityComparer _comparer;
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
yield return describe.Singleton<IActionSelectorDecisionTreeProvider, ActionSelectorDecisionTreeProvider>();
|
||||
yield return describe.Scoped<IActionSelector, DefaultActionSelector>();
|
||||
|
||||
// This provider needs access to the per-request services, but might be used many times for a given
|
||||
// request.
|
||||
yield return describe.Scoped<INestedProvider<ActionConstraintProviderContext>,
|
||||
DefaultActionConstraintProvider>();
|
||||
|
||||
yield return describe.Transient<IActionInvokerFactory, ActionInvokerFactory>();
|
||||
yield return describe.Transient<IControllerAssemblyProvider, DefaultControllerAssemblyProvider>();
|
||||
yield return describe.Transient<IActionDiscoveryConventions, DefaultActionDiscoveryConventions>();
|
||||
|
|
|
|||
|
|
@ -178,9 +178,16 @@ namespace Microsoft.AspNet.Mvc
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -82,9 +82,16 @@ namespace Microsoft.AspNet.Mvc
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.True(action.RequireActionNameMatch);
|
||||
Assert.Null(action.HttpMethods);
|
||||
Assert.Null(action.AttributeRoute);
|
||||
Assert.Empty(action.Attributes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -185,6 +186,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.True(action.RequireActionNameMatch);
|
||||
Assert.Equal(new[] { "PUT", "PATCH" }, action.HttpMethods);
|
||||
Assert.Null(action.AttributeRoute);
|
||||
Assert.IsType<CustomHttpMethodsAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -206,6 +208,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
var httpMethod = Assert.Single(action.HttpMethods);
|
||||
Assert.Equal("DELETE", httpMethod);
|
||||
Assert.Null(action.AttributeRoute);
|
||||
|
||||
Assert.IsType<HttpDeleteAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -225,6 +229,10 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.True(action.RequireActionNameMatch);
|
||||
Assert.Equal(new[] { "GET", "POST" }, action.HttpMethods.OrderBy(m => m, StringComparer.Ordinal));
|
||||
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]
|
||||
|
|
@ -244,6 +252,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.True(action.RequireActionNameMatch);
|
||||
Assert.Equal(new[] { "GET", "POST", "PUT" }, action.HttpMethods.OrderBy(m => m, StringComparer.Ordinal));
|
||||
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]
|
||||
|
|
@ -268,6 +281,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
Assert.NotNull(action.AttributeRoute);
|
||||
Assert.Equal("Change", action.AttributeRoute.Template);
|
||||
|
||||
Assert.IsType<HttpPostAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -291,6 +306,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
Assert.NotNull(action.AttributeRoute);
|
||||
Assert.Equal("Update", action.AttributeRoute.Template);
|
||||
|
||||
Assert.IsType<RouteAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -314,6 +331,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
Assert.NotNull(action.AttributeRoute);
|
||||
Assert.Equal("ListAll", action.AttributeRoute.Template);
|
||||
|
||||
Assert.IsType<AcceptVerbsAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -341,10 +360,12 @@ namespace Microsoft.AspNet.Mvc
|
|||
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]
|
||||
|
|
@ -367,6 +388,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.Null(action.HttpMethods);
|
||||
|
||||
Assert.Null(action.AttributeRoute);
|
||||
|
||||
Assert.Empty(action.Attributes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -390,6 +413,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.Null(action.HttpMethods);
|
||||
|
||||
Assert.Null(action.AttributeRoute);
|
||||
|
||||
Assert.Empty(action.Attributes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -416,6 +441,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.Equal("GET", httpMethod);
|
||||
|
||||
Assert.NotNull(action.AttributeRoute);
|
||||
|
||||
Assert.IsType<HttpGetAttribute>(Assert.Single(action.Attributes));
|
||||
}
|
||||
|
||||
Assert.Single(actionInfos, ai => ai.AttributeRoute.Template.Equals("List"));
|
||||
|
|
@ -745,22 +772,22 @@ namespace Microsoft.AspNet.Mvc.DefaultActionDiscoveryConventionsControllers
|
|||
[HttpPut]
|
||||
[AcceptVerbs("GET", "POST")]
|
||||
public void List() { }
|
||||
}
|
||||
|
||||
// Keep it private and nested to avoid polluting the namespace.
|
||||
private class CustomHttpMethodsAttribute : Attribute, IActionHttpMethodProvider
|
||||
public class CustomHttpMethodsAttribute : Attribute, IActionHttpMethodProvider
|
||||
{
|
||||
private readonly string[] _methods;
|
||||
|
||||
public CustomHttpMethodsAttribute(params string[] methods)
|
||||
{
|
||||
private readonly string[] _methods;
|
||||
_methods = methods;
|
||||
}
|
||||
|
||||
public CustomHttpMethodsAttribute(params string[] methods)
|
||||
public IEnumerable<string> HttpMethods
|
||||
{
|
||||
get
|
||||
{
|
||||
_methods = methods;
|
||||
}
|
||||
public IEnumerable<string> HttpMethods
|
||||
{
|
||||
get
|
||||
{
|
||||
return _methods;
|
||||
}
|
||||
return _methods;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.AspNet.Mvc.Logging;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.DependencyInjection.NestedProviders;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -52,8 +55,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
var values = Assert.IsType<DefaultActionSelectorSelectAsyncValues>(write.State);
|
||||
Assert.Equal("DefaultActionSelector.SelectAsync", values.Name);
|
||||
Assert.Empty(values.ActionsMatchingRouteConstraints);
|
||||
Assert.Empty(values.ActionsMatchingRouteAndMethodConstraints);
|
||||
Assert.Empty(values.ActionsMatchingRouteAndMethodAndDynamicConstraints);
|
||||
Assert.Empty(values.ActionsMatchingActionConstraints);
|
||||
Assert.Empty(values.FinalMatches);
|
||||
Assert.Null(values.SelectedAction);
|
||||
}
|
||||
|
|
@ -67,7 +69,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
var matched = new ActionDescriptor()
|
||||
{
|
||||
MethodConstraints = new List<HttpMethodConstraint>()
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new HttpMethodConstraint(new string[] { "POST" }),
|
||||
},
|
||||
|
|
@ -107,7 +109,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
var values = Assert.IsType<DefaultActionSelectorSelectAsyncValues>(write.State);
|
||||
Assert.Equal("DefaultActionSelector.SelectAsync", values.Name);
|
||||
Assert.Equal<ActionDescriptor>(actions, values.ActionsMatchingRouteConstraints);
|
||||
Assert.Equal<ActionDescriptor>(actions, values.ActionsMatchingRouteAndMethodConstraints);
|
||||
Assert.Equal<ActionDescriptor>(new[] { matched }, values.ActionsMatchingActionConstraints);
|
||||
Assert.Equal(matched, Assert.Single(values.FinalMatches));
|
||||
Assert.Equal(matched, values.SelectedAction);
|
||||
}
|
||||
|
|
@ -155,7 +157,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
var values = Assert.IsType<DefaultActionSelectorSelectAsyncValues>(write.State);
|
||||
Assert.Equal("DefaultActionSelector.SelectAsync", values.Name);
|
||||
Assert.Equal<ActionDescriptor>(actions, values.ActionsMatchingRouteConstraints);
|
||||
Assert.Equal<ActionDescriptor>(actions, values.ActionsMatchingRouteAndMethodConstraints);
|
||||
Assert.Equal<ActionDescriptor>(actions, values.ActionsMatchingActionConstraints);
|
||||
Assert.Equal<ActionDescriptor>(actions, values.FinalMatches);
|
||||
Assert.Null(values.SelectedAction);
|
||||
}
|
||||
|
|
@ -200,7 +202,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
// Arrange
|
||||
var actionWithConstraints = new ActionDescriptor()
|
||||
{
|
||||
MethodConstraints = new List<HttpMethodConstraint>()
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new HttpMethodConstraint(new string[] { "POST" }),
|
||||
},
|
||||
|
|
@ -224,6 +226,268 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.Same(action, actionWithConstraints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SelectAsync_ConstraintsRejectAll()
|
||||
{
|
||||
// Arrange
|
||||
var action1 = new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new BooleanConstraint() { Pass = false, },
|
||||
},
|
||||
};
|
||||
|
||||
var action2 = new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new BooleanConstraint() { Pass = false, },
|
||||
},
|
||||
};
|
||||
|
||||
var actions = new ActionDescriptor[] { action1, action2 };
|
||||
|
||||
var selector = CreateSelector(actions);
|
||||
var context = CreateRouteContext("POST");
|
||||
|
||||
// Act
|
||||
var action = await selector.SelectAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SelectAsync_ConstraintsRejectAll_DifferentStages()
|
||||
{
|
||||
// Arrange
|
||||
var action1 = new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new BooleanConstraint() { Pass = false, Order = 0 },
|
||||
new BooleanConstraint() { Pass = true, Order = 1 },
|
||||
},
|
||||
};
|
||||
|
||||
var action2 = new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new BooleanConstraint() { Pass = true, Order = 0 },
|
||||
new BooleanConstraint() { Pass = false, Order = 1 },
|
||||
},
|
||||
};
|
||||
|
||||
var actions = new ActionDescriptor[] { action1, action2 };
|
||||
|
||||
var selector = CreateSelector(actions);
|
||||
var context = CreateRouteContext("POST");
|
||||
|
||||
// Act
|
||||
var action = await selector.SelectAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SelectAsync_ActionConstraintFactory()
|
||||
{
|
||||
// Arrange
|
||||
var actionWithConstraints = new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new ConstraintFactory()
|
||||
{
|
||||
Constraint = new BooleanConstraint() { Pass = true },
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var actionWithoutConstraints = new ActionDescriptor()
|
||||
{
|
||||
Parameters = new List<ParameterDescriptor>(),
|
||||
};
|
||||
|
||||
var actions = new ActionDescriptor[] { actionWithConstraints, actionWithoutConstraints };
|
||||
|
||||
var selector = CreateSelector(actions);
|
||||
var context = CreateRouteContext("POST");
|
||||
|
||||
// Act
|
||||
var action = await selector.SelectAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Same(action, actionWithConstraints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SelectAsync_ActionConstraintFactory_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var nullConstraint = new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new ConstraintFactory()
|
||||
{
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var actions = new ActionDescriptor[] { nullConstraint };
|
||||
|
||||
var selector = CreateSelector(actions);
|
||||
var context = CreateRouteContext("POST");
|
||||
|
||||
// Act
|
||||
var action = await selector.SelectAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Same(action, nullConstraint);
|
||||
}
|
||||
|
||||
// There's a custom constraint provider registered that only understands BooleanConstraintMarker
|
||||
[Fact]
|
||||
public async Task SelectAsync_CustomProvider()
|
||||
{
|
||||
// Arrange
|
||||
var actionWithConstraints = new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new BooleanConstraintMarker() { Pass = true },
|
||||
}
|
||||
};
|
||||
|
||||
var actionWithoutConstraints = new ActionDescriptor()
|
||||
{
|
||||
Parameters = new List<ParameterDescriptor>(),
|
||||
};
|
||||
|
||||
var actions = new ActionDescriptor[] { actionWithConstraints, actionWithoutConstraints, };
|
||||
|
||||
var selector = CreateSelector(actions);
|
||||
var context = CreateRouteContext("POST");
|
||||
|
||||
// Act
|
||||
var action = await selector.SelectAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Same(action, actionWithConstraints);
|
||||
}
|
||||
|
||||
// Due to ordering of stages, the first action will be better.
|
||||
[Fact]
|
||||
public async Task SelectAsync_ConstraintsInOrder()
|
||||
{
|
||||
// Arrange
|
||||
var best = new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new BooleanConstraint() { Pass = true, Order = 0, },
|
||||
},
|
||||
};
|
||||
|
||||
var worst = new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new BooleanConstraint() { Pass = true, Order = 1, },
|
||||
},
|
||||
};
|
||||
|
||||
var actions = new ActionDescriptor[] { best, worst };
|
||||
|
||||
var selector = CreateSelector(actions);
|
||||
var context = CreateRouteContext("POST");
|
||||
|
||||
// Act
|
||||
var action = await selector.SelectAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Same(action, best);
|
||||
}
|
||||
|
||||
// Due to ordering of stages, the first action will be better.
|
||||
[Fact]
|
||||
public async Task SelectAsync_ConstraintsInOrder_MultipleStages()
|
||||
{
|
||||
// Arrange
|
||||
var best = new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new BooleanConstraint() { Pass = true, Order = 0, },
|
||||
new BooleanConstraint() { Pass = true, Order = 1, },
|
||||
new BooleanConstraint() { Pass = true, Order = 2, },
|
||||
},
|
||||
};
|
||||
|
||||
var worst = new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new BooleanConstraint() { Pass = true, Order = 0, },
|
||||
new BooleanConstraint() { Pass = true, Order = 1, },
|
||||
new BooleanConstraint() { Pass = true, Order = 3, },
|
||||
},
|
||||
};
|
||||
|
||||
var actions = new ActionDescriptor[] { best, worst };
|
||||
|
||||
var selector = CreateSelector(actions);
|
||||
var context = CreateRouteContext("POST");
|
||||
|
||||
// Act
|
||||
var action = await selector.SelectAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Same(action, best);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SelectAsync_Fallback_ToActionWithoutConstraints()
|
||||
{
|
||||
// Arrange
|
||||
var nomatch1 = new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new BooleanConstraint() { Pass = true, Order = 0, },
|
||||
new BooleanConstraint() { Pass = true, Order = 1, },
|
||||
new BooleanConstraint() { Pass = false, Order = 2, },
|
||||
},
|
||||
};
|
||||
|
||||
var nomatch2 = new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new BooleanConstraint() { Pass = true, Order = 0, },
|
||||
new BooleanConstraint() { Pass = true, Order = 1, },
|
||||
new BooleanConstraint() { Pass = false, Order = 3, },
|
||||
},
|
||||
};
|
||||
|
||||
var best = new ActionDescriptor();
|
||||
|
||||
var actions = new ActionDescriptor[] { best, nomatch1, nomatch2 };
|
||||
|
||||
var selector = CreateSelector(actions);
|
||||
var context = CreateRouteContext("POST");
|
||||
|
||||
// Act
|
||||
var action = await selector.SelectAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Same(action, best);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SelectAsync_WithCatchAll_PrefersNonCatchAll()
|
||||
{
|
||||
|
|
@ -389,7 +653,18 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
var decisionTreeProvider = new ActionSelectorDecisionTreeProvider(actionProvider.Object);
|
||||
|
||||
return new DefaultActionSelector(actionProvider.Object, decisionTreeProvider, loggerFactory);
|
||||
var actionConstraintProvider = new NestedProviderManager<ActionConstraintProviderContext>(
|
||||
new INestedProvider<ActionConstraintProviderContext>[]
|
||||
{
|
||||
new DefaultActionConstraintProvider(new ServiceContainer()),
|
||||
new BooleanConstraintProvider(),
|
||||
});
|
||||
|
||||
return new DefaultActionSelector(
|
||||
actionProvider.Object,
|
||||
decisionTreeProvider,
|
||||
actionConstraintProvider,
|
||||
loggerFactory);
|
||||
}
|
||||
|
||||
private static VirtualPathContext CreateContext(object routeValues)
|
||||
|
|
@ -454,5 +729,52 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
return actionDescriptor;
|
||||
}
|
||||
|
||||
private class BooleanConstraint : IActionConstraint
|
||||
{
|
||||
public bool Pass { get; set; }
|
||||
|
||||
public int Order { get; set; }
|
||||
|
||||
public bool Accept([NotNull]ActionConstraintContext context)
|
||||
{
|
||||
return Pass;
|
||||
}
|
||||
}
|
||||
|
||||
private class ConstraintFactory : IActionConstraintFactory
|
||||
{
|
||||
public IActionConstraint Constraint { get; set; }
|
||||
|
||||
public IActionConstraint CreateInstance(IServiceProvider services)
|
||||
{
|
||||
return Constraint;
|
||||
}
|
||||
}
|
||||
|
||||
private class BooleanConstraintMarker : IActionConstraintMetadata
|
||||
{
|
||||
public bool Pass { get; set; }
|
||||
}
|
||||
|
||||
private class BooleanConstraintProvider : INestedProvider<ActionConstraintProviderContext>
|
||||
{
|
||||
public int Order { get; set; }
|
||||
|
||||
public void Invoke(ActionConstraintProviderContext context, Action callNext)
|
||||
{
|
||||
foreach (var item in context.Results)
|
||||
{
|
||||
var marker = item.Metadata as BooleanConstraintMarker;
|
||||
if (marker != null)
|
||||
{
|
||||
Assert.Null(item.Constraint);
|
||||
item.Constraint = new BooleanConstraint() { Pass = marker.Pass };
|
||||
}
|
||||
}
|
||||
|
||||
callNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ namespace Microsoft.AspNet.Mvc.Description
|
|||
{
|
||||
// Arrange
|
||||
var action = CreateActionDescriptor();
|
||||
action.MethodConstraints = new List<HttpMethodConstraint>()
|
||||
action.ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new HttpMethodConstraint(new string[] { "PUT", "POST" }),
|
||||
new HttpMethodConstraint(new string[] { "GET" }),
|
||||
|
|
|
|||
|
|
@ -73,8 +73,8 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
// Assert
|
||||
Assert.Equal("OnlyPost", descriptor.Name);
|
||||
|
||||
Assert.Single(descriptor.MethodConstraints);
|
||||
Assert.Equal(new string[] { "POST" }, descriptor.MethodConstraints[0].HttpMethods);
|
||||
var constraint = Assert.IsType<HttpMethodConstraint>(Assert.Single(descriptor.ActionConstraints));
|
||||
Assert.Equal(new string[] { "POST" }, constraint.HttpMethods);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -646,8 +646,8 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
{
|
||||
Assert.Equal("MultiRouteAttributes", action.ControllerName);
|
||||
|
||||
Assert.NotNull(action.MethodConstraints);
|
||||
var methodConstraint = Assert.Single(action.MethodConstraints);
|
||||
Assert.NotNull(action.ActionConstraints);
|
||||
var methodConstraint = Assert.IsType<HttpMethodConstraint>(Assert.Single(action.ActionConstraints));
|
||||
|
||||
Assert.NotNull(methodConstraint.HttpMethods);
|
||||
Assert.Equal(new[] { "POST" }, methodConstraint.HttpMethods);
|
||||
|
|
@ -671,8 +671,8 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
|
||||
Assert.Equal("MultiRouteAttributes", action.ControllerName);
|
||||
|
||||
Assert.NotNull(action.MethodConstraints);
|
||||
var methodConstraint = Assert.Single(action.MethodConstraints);
|
||||
Assert.NotNull(action.ActionConstraints);
|
||||
var methodConstraint = Assert.IsType<HttpMethodConstraint>(Assert.Single(action.ActionConstraints));
|
||||
|
||||
Assert.NotNull(methodConstraint.HttpMethods);
|
||||
Assert.Equal(new[] { "PUT" }, methodConstraint.HttpMethods);
|
||||
|
|
@ -702,23 +702,25 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
Assert.NotNull(action.AttributeRouteInfo.Template);
|
||||
}
|
||||
|
||||
var constrainedActions = actions.Where(a => a.MethodConstraints != null);
|
||||
var constrainedActions = actions.Where(a => a.ActionConstraints != null);
|
||||
Assert.Equal(4, constrainedActions.Count());
|
||||
|
||||
// Actions generated by AcceptVerbs
|
||||
var postActions = constrainedActions.Where(a => a.MethodConstraints.Single().HttpMethods.Single() == "POST");
|
||||
var postActions = constrainedActions.Where(
|
||||
a => a.ActionConstraints.OfType<HttpMethodConstraint>().Single().HttpMethods.Single() == "POST");
|
||||
Assert.Equal(2, postActions.Count());
|
||||
Assert.Single(postActions, a => a.AttributeRouteInfo.Template.Equals("v1"));
|
||||
Assert.Single(postActions, a => a.AttributeRouteInfo.Template.Equals("v2"));
|
||||
|
||||
// Actions generated by PutAttribute
|
||||
var putActions = constrainedActions.Where(a => a.MethodConstraints.Single().HttpMethods.Single() == "PUT");
|
||||
var putActions = constrainedActions.Where(
|
||||
a => a.ActionConstraints.OfType<HttpMethodConstraint>().Single().HttpMethods.Single() == "PUT");
|
||||
Assert.Equal(2, putActions.Count());
|
||||
Assert.Single(putActions, a => a.AttributeRouteInfo.Template.Equals("v1/All"));
|
||||
Assert.Single(putActions, a => a.AttributeRouteInfo.Template.Equals("v2/All"));
|
||||
|
||||
// Actions generated by RouteAttribute
|
||||
var unconstrainedActions = actions.Where(a => a.MethodConstraints == null);
|
||||
var unconstrainedActions = actions.Where(a => a.ActionConstraints == null);
|
||||
Assert.Equal(2, unconstrainedActions.Count());
|
||||
Assert.Single(unconstrainedActions, a => a.AttributeRouteInfo.Template.Equals("v1/List"));
|
||||
Assert.Single(unconstrainedActions, a => a.AttributeRouteInfo.Template.Equals("v2/List"));
|
||||
|
|
@ -766,7 +768,10 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
{
|
||||
var action = Assert.Single(
|
||||
actions,
|
||||
a => a.MethodConstraints.SelectMany(c => c.HttpMethods).Contains(method));
|
||||
a => a.ActionConstraints
|
||||
.OfType<HttpMethodConstraint>()
|
||||
.SelectMany(c => c.HttpMethods)
|
||||
.Contains(method));
|
||||
|
||||
Assert.NotNull(action.AttributeRouteInfo);
|
||||
Assert.Equal("Products/list", action.AttributeRouteInfo.Template);
|
||||
|
|
@ -827,7 +832,7 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
Assert.NotNull(action.AttributeRouteInfo);
|
||||
Assert.Equal("Products/Index", action.AttributeRouteInfo.Template);
|
||||
|
||||
Assert.Null(action.MethodConstraints);
|
||||
Assert.Null(action.ActionConstraints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1180,6 +1185,149 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
Assert.Equal(4, sequence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildModel_SplitsConstraintsBasedOnRoute()
|
||||
{
|
||||
// Arrange
|
||||
var provider = GetProvider(typeof(MultipleRouteProviderOnActionController).GetTypeInfo());
|
||||
|
||||
// Act
|
||||
var model = provider.BuildModel();
|
||||
|
||||
// Assert
|
||||
var actions = Assert.Single(model.Controllers).Actions;
|
||||
Assert.Equal(2, actions.Count());
|
||||
|
||||
var action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "R1");
|
||||
|
||||
Assert.Equal(2, action.Attributes.Count);
|
||||
Assert.Single(action.Attributes, a => a is RouteAndConstraintAttribute);
|
||||
Assert.Single(action.Attributes, a => a is ConstraintAttribute);
|
||||
|
||||
Assert.Equal(2, action.ActionConstraints.Count);
|
||||
Assert.Single(action.ActionConstraints, a => a is RouteAndConstraintAttribute);
|
||||
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
|
||||
|
||||
action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "R2");
|
||||
|
||||
Assert.Equal(2, action.Attributes.Count);
|
||||
Assert.Single(action.Attributes, a => a is RouteAndConstraintAttribute);
|
||||
Assert.Single(action.Attributes, a => a is ConstraintAttribute);
|
||||
|
||||
Assert.Equal(2, action.ActionConstraints.Count);
|
||||
Assert.Single(action.ActionConstraints, a => a is RouteAndConstraintAttribute);
|
||||
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_SplitsConstraintsBasedOnRoute()
|
||||
{
|
||||
// Arrange
|
||||
var provider = GetProvider(typeof(MultipleRouteProviderOnActionController).GetTypeInfo());
|
||||
|
||||
// Act
|
||||
var actions = provider.GetDescriptors();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, actions.Count());
|
||||
|
||||
var action = Assert.Single(actions, a => a.AttributeRouteInfo.Template == "R1");
|
||||
|
||||
Assert.Equal(2, action.ActionConstraints.Count);
|
||||
Assert.Single(action.ActionConstraints, a => a is RouteAndConstraintAttribute);
|
||||
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
|
||||
|
||||
action = Assert.Single(actions, a => a.AttributeRouteInfo.Template == "R2");
|
||||
|
||||
Assert.Equal(2, action.ActionConstraints.Count);
|
||||
Assert.Single(action.ActionConstraints, a => a is RouteAndConstraintAttribute);
|
||||
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_SplitsConstraintsBasedOnControllerRoute()
|
||||
{
|
||||
// Arrange
|
||||
var actionName = nameof(MultipleRouteProviderOnActionAndControllerController.Edit);
|
||||
var provider = GetProvider(typeof(MultipleRouteProviderOnActionAndControllerController).GetTypeInfo());
|
||||
|
||||
// Act
|
||||
var actions = provider.GetDescriptors().Where(a => a.Name == actionName);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, actions.Count());
|
||||
|
||||
var action = Assert.Single(actions, a => a.AttributeRouteInfo.Template == "C1/A1");
|
||||
Assert.Equal(3, action.ActionConstraints.Count);
|
||||
Assert.Single(action.ActionConstraints, a => (a as RouteAndConstraintAttribute)?.Template == "C1");
|
||||
Assert.Single(action.ActionConstraints, a => (a as RouteAndConstraintAttribute)?.Template == "A1");
|
||||
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
|
||||
|
||||
action = Assert.Single(actions, a => a.AttributeRouteInfo.Template == "C2/A1");
|
||||
Assert.Equal(3, action.ActionConstraints.Count);
|
||||
Assert.Single(action.ActionConstraints, a => (a as RouteAndConstraintAttribute)?.Template == "C2");
|
||||
Assert.Single(action.ActionConstraints, a => (a as RouteAndConstraintAttribute)?.Template == "A1");
|
||||
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDescriptors_SplitsConstraintsBasedOnControllerRoute_MultipleRoutesOnAction()
|
||||
{
|
||||
// Arrange
|
||||
var actionName = nameof(MultipleRouteProviderOnActionAndControllerController.Delete);
|
||||
var provider = GetProvider(typeof(MultipleRouteProviderOnActionAndControllerController).GetTypeInfo());
|
||||
|
||||
// Act
|
||||
var actions = provider.GetDescriptors().Where(a => a.Name == actionName);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(4, actions.Count());
|
||||
|
||||
var action = Assert.Single(actions, a => a.AttributeRouteInfo.Template == "C1/A3");
|
||||
Assert.Equal(3, action.ActionConstraints.Count);
|
||||
Assert.Single(action.ActionConstraints, a => (a as RouteAndConstraintAttribute)?.Template == "C1");
|
||||
Assert.Single(action.ActionConstraints, a => (a as RouteAndConstraintAttribute)?.Template == "A3");
|
||||
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
|
||||
|
||||
action = Assert.Single(actions, a => a.AttributeRouteInfo.Template == "C2/A3");
|
||||
Assert.Equal(3, action.ActionConstraints.Count);
|
||||
Assert.Single(action.ActionConstraints, a => (a as RouteAndConstraintAttribute)?.Template == "C2");
|
||||
Assert.Single(action.ActionConstraints, a => (a as RouteAndConstraintAttribute)?.Template == "A3");
|
||||
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
|
||||
|
||||
action = Assert.Single(actions, a => a.AttributeRouteInfo.Template == "C1/A4");
|
||||
Assert.Equal(3, action.ActionConstraints.Count);
|
||||
Assert.Single(action.ActionConstraints, a => (a as RouteAndConstraintAttribute)?.Template == "C1");
|
||||
Assert.Single(action.ActionConstraints, a => (a as RouteAndConstraintAttribute)?.Template == "A4");
|
||||
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
|
||||
|
||||
action = Assert.Single(actions, a => a.AttributeRouteInfo.Template == "C2/A4");
|
||||
Assert.Equal(3, action.ActionConstraints.Count);
|
||||
Assert.Single(action.ActionConstraints, a => (a as RouteAndConstraintAttribute)?.Template == "C2");
|
||||
Assert.Single(action.ActionConstraints, a => (a as RouteAndConstraintAttribute)?.Template == "A4");
|
||||
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
|
||||
}
|
||||
|
||||
// This method overrides the route from the controller, and so doesn't inherit its metadata.
|
||||
[Fact]
|
||||
public void GetDescriptors_SplitsConstraintsBasedOnControllerRoute_Override()
|
||||
{
|
||||
// Arrange
|
||||
var actionName = nameof(MultipleRouteProviderOnActionAndControllerController.Create);
|
||||
var provider = GetProvider(typeof(MultipleRouteProviderOnActionAndControllerController).GetTypeInfo());
|
||||
|
||||
// Act
|
||||
var actions = provider.GetDescriptors().Where(a => a.Name == actionName);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, actions.Count());
|
||||
|
||||
var action = Assert.Single(actions, a => a.AttributeRouteInfo.Template == "A2");
|
||||
Assert.Equal(2, action.ActionConstraints.Count);
|
||||
Assert.Single(action.ActionConstraints, a => (a as RouteAndConstraintAttribute)?.Template == "~/A2");
|
||||
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
|
||||
}
|
||||
|
||||
private ReflectedActionDescriptorProvider GetProvider(
|
||||
TypeInfo controllerTypeInfo,
|
||||
IEnumerable<IFilter> filters = null)
|
||||
|
|
@ -1658,5 +1806,49 @@ namespace Microsoft.AspNet.Mvc.Test
|
|||
{
|
||||
public void Create(int productId) { }
|
||||
}
|
||||
|
||||
private class MultipleRouteProviderOnActionController
|
||||
{
|
||||
[Constraint]
|
||||
[RouteAndConstraint("R1")]
|
||||
[RouteAndConstraint("R2")]
|
||||
public void Edit() { }
|
||||
}
|
||||
|
||||
[Constraint]
|
||||
[RouteAndConstraint("C1")]
|
||||
[RouteAndConstraint("C2")]
|
||||
private class MultipleRouteProviderOnActionAndControllerController
|
||||
{
|
||||
[RouteAndConstraint("A1")]
|
||||
public void Edit() { }
|
||||
|
||||
[RouteAndConstraint("~/A2")]
|
||||
public void Create() { }
|
||||
|
||||
[RouteAndConstraint("A3")]
|
||||
[RouteAndConstraint("A4")]
|
||||
public void Delete() { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
|
||||
private class RouteAndConstraintAttribute : Attribute, IActionConstraintMetadata, IRouteTemplateProvider
|
||||
{
|
||||
public RouteAndConstraintAttribute(string template)
|
||||
{
|
||||
Template = template;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public int? Order { get; set; }
|
||||
|
||||
public string Template { get; private set; }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
|
||||
private class ConstraintAttribute : Attribute, IActionConstraintMetadata
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,93 +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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
|
||||
{
|
||||
public class ReflectedActionModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReflectedActionModel_PopulatesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var actionMethod = typeof(BlogController).GetMethod("Edit");
|
||||
|
||||
// Act
|
||||
var model = new ReflectedActionModel(actionMethod);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, model.Attributes.Count);
|
||||
Assert.Single(model.Attributes, a => a is MyFilterAttribute);
|
||||
Assert.Single(model.Attributes, a => a is MyOtherAttribute);
|
||||
Assert.Single(model.Attributes, a => a is HttpGetAttribute);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReflectedActionModel_PopulatesFilters()
|
||||
{
|
||||
// Arrange
|
||||
var actionMethod = typeof(BlogController).GetMethod("Edit");
|
||||
|
||||
// Act
|
||||
var model = new ReflectedActionModel(actionMethod);
|
||||
|
||||
// Assert
|
||||
Assert.Single(model.Filters);
|
||||
Assert.IsType<MyFilterAttribute>(model.Filters[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReflectedActionModel_PopulatesApiExplorerInfo()
|
||||
{
|
||||
// Arrange
|
||||
var actionMethod = typeof(BlogController).GetMethod("Create");
|
||||
|
||||
// Act
|
||||
var model = new ReflectedActionModel(actionMethod);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(false, model.ApiExplorerIsVisible);
|
||||
Assert.Equal("Blog", model.ApiExplorerGroupName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReflectedActionModel_PopulatesApiExplorerInfo_NoAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var actionMethod = typeof(BlogController).GetMethod("Edit");
|
||||
|
||||
// Act
|
||||
var model = new ReflectedActionModel(actionMethod);
|
||||
|
||||
// Assert
|
||||
Assert.Null(model.ApiExplorerIsVisible);
|
||||
Assert.Null(model.ApiExplorerGroupName);
|
||||
}
|
||||
|
||||
private class BlogController
|
||||
{
|
||||
[MyOther]
|
||||
[MyFilter]
|
||||
[HttpGet("Edit")]
|
||||
public void Edit()
|
||||
{
|
||||
}
|
||||
|
||||
[ApiExplorerSettings(IgnoreApi = true, GroupName = "Blog")]
|
||||
public void Create()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private class MyFilterAttribute : Attribute, IFilter
|
||||
{
|
||||
}
|
||||
|
||||
private class MyOtherAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
|
||||
{
|
||||
public class ReflectedControllerModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReflectedControllerModel_PopulatesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var controllerType = typeof(BlogController);
|
||||
|
||||
// Act
|
||||
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
Assert.Equal(6, model.Attributes.Count);
|
||||
|
||||
Assert.Single(model.Attributes, a => a is MyOtherAttribute);
|
||||
Assert.Single(model.Attributes, a => a is MyFilterAttribute);
|
||||
Assert.Single(model.Attributes, a => a is MyRouteConstraintAttribute);
|
||||
Assert.Single(model.Attributes, a => a is ApiExplorerSettingsAttribute);
|
||||
|
||||
var routes = model.Attributes.OfType<RouteAttribute>().ToList();
|
||||
Assert.Equal(2, routes.Count());
|
||||
Assert.Single(routes, r => r.Template.Equals("Blog"));
|
||||
Assert.Single(routes, r => r.Template.Equals("Microblog"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReflectedControllerModel_PopulatesFilters()
|
||||
{
|
||||
// Arrange
|
||||
var controllerType = typeof(BlogController);
|
||||
|
||||
// Act
|
||||
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
Assert.Single(model.Filters);
|
||||
Assert.IsType<MyFilterAttribute>(model.Filters[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReflectedControllerModel_PopulatesRouteConstraintAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var controllerType = typeof(BlogController);
|
||||
|
||||
// Act
|
||||
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
Assert.Single(model.RouteConstraints);
|
||||
Assert.IsType<MyRouteConstraintAttribute>(model.RouteConstraints[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReflectedControllerModel_ComputesControllerName()
|
||||
{
|
||||
// Arrange
|
||||
var controllerType = typeof(BlogController);
|
||||
|
||||
// Act
|
||||
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Blog", model.ControllerName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReflectedControllerModel_ComputesControllerName_WithoutSuffix()
|
||||
{
|
||||
// Arrange
|
||||
var controllerType = typeof(Store);
|
||||
|
||||
// Act
|
||||
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Store", model.ControllerName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReflectedControllerModel_PopulatesAttributeRouteInfo()
|
||||
{
|
||||
// Arrange
|
||||
var controllerType = typeof(BlogController);
|
||||
|
||||
// Act
|
||||
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(model.AttributeRoutes);
|
||||
Assert.Equal(2, model.AttributeRoutes.Count); ;
|
||||
Assert.Single(model.AttributeRoutes, r => r.Template.Equals("Blog"));
|
||||
Assert.Single(model.AttributeRoutes, r => r.Template.Equals("Microblog"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReflectedControllerModel_PopulatesApiExplorerInfo()
|
||||
{
|
||||
// Arrange
|
||||
var controllerType = typeof(BlogController);
|
||||
|
||||
// Act
|
||||
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
Assert.Equal(true, model.ApiExplorerIsVisible);
|
||||
Assert.Equal("Blog", model.ApiExplorerGroupName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReflectedControllerModel_PopulatesApiExplorerInfo_Inherited()
|
||||
{
|
||||
// Arrange
|
||||
var controllerType = typeof(DerivedController);
|
||||
|
||||
// Act
|
||||
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
Assert.Equal(true, model.ApiExplorerIsVisible);
|
||||
Assert.Equal("API", model.ApiExplorerGroupName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReflectedControllerModel_PopulatesApiExplorerInfo_NoAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var controllerType = typeof(Store);
|
||||
|
||||
// Act
|
||||
var model = new ReflectedControllerModel(controllerType.GetTypeInfo());
|
||||
|
||||
// Assert
|
||||
Assert.Null(model.ApiExplorerIsVisible);
|
||||
Assert.Null(model.ApiExplorerGroupName);
|
||||
}
|
||||
|
||||
[MyOther]
|
||||
[MyFilter]
|
||||
[MyRouteConstraint]
|
||||
[Route("Blog")]
|
||||
[Route("Microblog")]
|
||||
[ApiExplorerSettings(GroupName = "Blog")]
|
||||
private class BlogController
|
||||
{
|
||||
}
|
||||
|
||||
private class Store
|
||||
{
|
||||
}
|
||||
|
||||
private class DerivedController : BaseController
|
||||
{
|
||||
}
|
||||
|
||||
[ApiExplorerSettings(GroupName = "API")]
|
||||
private class BaseController
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private class MyRouteConstraintAttribute : RouteConstraintAttribute
|
||||
{
|
||||
public MyRouteConstraintAttribute()
|
||||
: base("MyRouteConstraint", "MyRouteConstraint", false)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class MyFilterAttribute : Attribute, IFilter
|
||||
{
|
||||
}
|
||||
|
||||
private class MyOtherAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder.Test
|
||||
{
|
||||
public class ReflectedParameterModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReflectedParameterModel_PopulatesAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var parameterInfo = typeof(BlogController).GetMethod("Edit").GetParameters()[0];
|
||||
|
||||
// Act
|
||||
var model = new ReflectedParameterModel(parameterInfo);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, model.Attributes.Count);
|
||||
Assert.Single(model.Attributes, a => a is MyOtherAttribute);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReflectedParameterModel_PopulatesParameterName()
|
||||
{
|
||||
// Arrange
|
||||
var parameterInfo = typeof(BlogController).GetMethod("Edit").GetParameters()[0];
|
||||
|
||||
// Act
|
||||
var model = new ReflectedParameterModel(parameterInfo);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("name", model.ParameterName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, false)]
|
||||
[InlineData(1, true)]
|
||||
public void ReflectedParameterModel_PopulatesIsOptional(int parameterIndex, bool expected)
|
||||
{
|
||||
// Arrange
|
||||
var parameterInfo = typeof(BlogController).GetMethod("Edit").GetParameters()[parameterIndex];
|
||||
|
||||
// Act
|
||||
var model = new ReflectedParameterModel(parameterInfo);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, model.IsOptional);
|
||||
}
|
||||
|
||||
private class BlogController
|
||||
{
|
||||
public void Edit([MyOther] string name, int age = 17)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class MyOtherAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue