From e6ba1f23a219ffb8df3592ac2b05b34b1dd3d57d Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 10 Jun 2014 14:16:13 -0700 Subject: [PATCH] refactor of action descriptor pipeline (cherry picked from commit 634f756e7fd303fc3022563fcd8fb9b1cb47fba2) --- ...outeConstraintsActionDescriptorProvider.cs | 68 ----- .../DefaultControllerDescriptorFactory.cs | 15 - .../DefaultParameterDescriptorFactory.cs | 37 --- .../IControllerDescriptorFactory.cs | 12 - .../IParameterDescriptorFactory.cs | 12 - .../Microsoft.AspNet.Mvc.Core.kproj | 12 +- src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs | 9 + .../ReflectedActionDescriptorProvider.cs | 226 ++++++++++----- .../IReflectedApplicationModelConvention.cs | 10 + .../ReflectedActionModel.cs | 40 +++ .../ReflectedApplicationModel.cs | 20 ++ .../ReflectedControllerModel.cs | 43 +++ .../ReflectedParameterModel.cs | 32 +++ src/Microsoft.AspNet.Mvc/MvcServices.cs | 4 - .../ActionAttributeTests.cs | 12 +- .../ActionSelectionConventionTests.cs | 9 +- .../Microsoft.AspNet.Mvc.Core.Test.kproj | 5 + .../MockMvcOptionsAccessor.cs | 17 ++ .../ReflectedActionDescriptorProviderTests.cs | 271 ++++++++++++++++-- .../ReflectedActionModelTests.cs | 57 ++++ .../ReflectedControllerModelTests.cs | 110 +++++++ .../ReflectedParameterModelTests.cs | 64 +++++ .../StaticActionDiscoveryConventions.cs | 28 ++ 23 files changed, 852 insertions(+), 261 deletions(-) delete mode 100644 src/Microsoft.AspNet.Mvc.Core/Areas/ReflectedRouteConstraintsActionDescriptorProvider.cs delete mode 100644 src/Microsoft.AspNet.Mvc.Core/DefaultControllerDescriptorFactory.cs delete mode 100644 src/Microsoft.AspNet.Mvc.Core/DefaultParameterDescriptorFactory.cs delete mode 100644 src/Microsoft.AspNet.Mvc.Core/IControllerDescriptorFactory.cs delete mode 100644 src/Microsoft.AspNet.Mvc.Core/IParameterDescriptorFactory.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/IReflectedApplicationModelConvention.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedActionModel.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedApplicationModel.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedControllerModel.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedParameterModel.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/MockMvcOptionsAccessor.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ReflectedModelBuilder/ReflectedActionModelTests.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ReflectedModelBuilder/ReflectedControllerModelTests.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ReflectedModelBuilder/ReflectedParameterModelTests.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/StaticActionDiscoveryConventions.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/Areas/ReflectedRouteConstraintsActionDescriptorProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Areas/ReflectedRouteConstraintsActionDescriptorProvider.cs deleted file mode 100644 index 280ec726e8..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/Areas/ReflectedRouteConstraintsActionDescriptorProvider.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace Microsoft.AspNet.Mvc -{ - public class ReflectedRouteConstraintsActionDescriptorProvider : IActionDescriptorProvider - { - public int Order - { - get { return ReflectedActionDescriptorProvider.DefaultOrder + 100; } - } - - public void Invoke([NotNull]ActionDescriptorProviderContext context, Action callNext) - { - var removalConstraints = new HashSet(StringComparer.OrdinalIgnoreCase); - - // Iterate all the Reflected Action Descriptor providers and add area or other route constraints - foreach (var actionDescriptor in context.Results.OfType()) - { - var routeConstraints = actionDescriptor - .ControllerDescriptor - .ControllerTypeInfo - .GetCustomAttributes() - .ToArray(); - - foreach (var routeConstraint in routeConstraints) - { - if (routeConstraint.BlockNonAttributedActions) - { - removalConstraints.Add(routeConstraint.RouteKey); - } - - // Skip duplicates - if (!HasConstraint(actionDescriptor, routeConstraint.RouteKey)) - { - actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( - routeConstraint.RouteKey, routeConstraint.RouteValue)); - } - } - } - - foreach (var actionDescriptor in context.Results.OfType()) - { - foreach (var key in removalConstraints) - { - if (!HasConstraint(actionDescriptor, key)) - { - actionDescriptor.RouteConstraints.Add( - new RouteDataActionConstraint(key, RouteKeyHandling.DenyKey)); - } - } - } - - callNext(); - } - - private bool HasConstraint(ActionDescriptor actionDescript, string routeKey) - { - return actionDescript.RouteConstraints.Any( - rc => string.Equals(rc.RouteKey, routeKey, StringComparison.OrdinalIgnoreCase)); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerDescriptorFactory.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerDescriptorFactory.cs deleted file mode 100644 index 1dfc113936..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerDescriptorFactory.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Reflection; - -namespace Microsoft.AspNet.Mvc -{ - public class DefaultControllerDescriptorFactory : IControllerDescriptorFactory - { - public ControllerDescriptor CreateControllerDescriptor(TypeInfo typeInfo) - { - return new ControllerDescriptor(typeInfo); - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultParameterDescriptorFactory.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultParameterDescriptorFactory.cs deleted file mode 100644 index 78aeff7c61..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultParameterDescriptorFactory.cs +++ /dev/null @@ -1,37 +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.Reflection; - -namespace Microsoft.AspNet.Mvc -{ - public class DefaultParameterDescriptorFactory : IParameterDescriptorFactory - { - public ParameterDescriptor GetDescriptor(ParameterInfo parameter) - { - var isFromBody = IsFromBody(parameter); - - return new ParameterDescriptor - { - Name = parameter.Name, - IsOptional = parameter.IsOptional || parameter.HasDefaultValue, - ParameterBindingInfo = isFromBody ? null : GetParameterBindingInfo(parameter), - BodyParameterInfo = isFromBody ? GetBodyParameterInfo(parameter) : null - }; - } - public virtual bool IsFromBody(ParameterInfo parameter) - { - return parameter.GetCustomAttribute() != null; - } - - private ParameterBindingInfo GetParameterBindingInfo(ParameterInfo parameter) - { - return new ParameterBindingInfo(parameter.Name, parameter.ParameterType); - } - - private BodyParameterInfo GetBodyParameterInfo(ParameterInfo parameter) - { - return new BodyParameterInfo(parameter.ParameterType); - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Core/IControllerDescriptorFactory.cs b/src/Microsoft.AspNet.Mvc.Core/IControllerDescriptorFactory.cs deleted file mode 100644 index 2d05153103..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/IControllerDescriptorFactory.cs +++ /dev/null @@ -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 System.Reflection; - -namespace Microsoft.AspNet.Mvc -{ - public interface IControllerDescriptorFactory - { - ControllerDescriptor CreateControllerDescriptor(TypeInfo type); - } -} diff --git a/src/Microsoft.AspNet.Mvc.Core/IParameterDescriptorFactory.cs b/src/Microsoft.AspNet.Mvc.Core/IParameterDescriptorFactory.cs deleted file mode 100644 index edae55b838..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/IParameterDescriptorFactory.cs +++ /dev/null @@ -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 System.Reflection; - -namespace Microsoft.AspNet.Mvc -{ - public interface IParameterDescriptorFactory - { - ParameterDescriptor GetDescriptor(ParameterInfo parameter); - } -} diff --git a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj index 5cdfa12834..8b33e8ec56 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj +++ b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj @@ -27,6 +27,10 @@ + + + + @@ -61,7 +65,6 @@ - @@ -69,9 +72,8 @@ - - + @@ -129,13 +131,11 @@ - - @@ -233,4 +233,4 @@ - + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs index f83c43479c..5e2fa586b9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using Microsoft.AspNet.Mvc.Core; +using Microsoft.AspNet.Mvc.ReflectedModelBuilder; namespace Microsoft.AspNet.Mvc { @@ -10,6 +12,11 @@ namespace Microsoft.AspNet.Mvc { private AntiForgeryOptions _antiForgeryOptions = new AntiForgeryOptions(); + public MvcOptions() + { + ApplicationModelConventions = new List(); + } + public AntiForgeryOptions AntiForgeryOptions { get @@ -29,5 +36,7 @@ namespace Microsoft.AspNet.Mvc _antiForgeryOptions = value; } } + + public List ApplicationModelConventions { get; private set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionDescriptorProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionDescriptorProvider.cs index a4a4d62fed..ef89347a0d 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionDescriptorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionDescriptorProvider.cs @@ -4,7 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; +#if K10 using System.Reflection; +#endif +using Microsoft.AspNet.Mvc.ReflectedModelBuilder; +using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc { @@ -14,23 +18,18 @@ namespace Microsoft.AspNet.Mvc private readonly IControllerAssemblyProvider _controllerAssemblyProvider; private readonly IActionDiscoveryConventions _conventions; - private readonly IControllerDescriptorFactory _controllerDescriptorFactory; - private readonly IParameterDescriptorFactory _parameterDescriptorFactory; - private readonly IEnumerable _globalFilters; + private readonly IEnumerable _globalFilters; + private readonly IEnumerable _modelConventions; public ReflectedActionDescriptorProvider(IControllerAssemblyProvider controllerAssemblyProvider, IActionDiscoveryConventions conventions, - IControllerDescriptorFactory controllerDescriptorFactory, - IParameterDescriptorFactory parameterDescriptorFactory, - IEnumerable globalFilters) + IEnumerable globalFilters, + IOptionsAccessor optionsAccessor) { _controllerAssemblyProvider = controllerAssemblyProvider; _conventions = conventions; - _controllerDescriptorFactory = controllerDescriptorFactory; - _parameterDescriptorFactory = parameterDescriptorFactory; - var filters = globalFilters ?? Enumerable.Empty(); - - _globalFilters = filters.Select(f => new FilterDescriptor(f, FilterScope.Global)); + _globalFilters = globalFilters ?? Enumerable.Empty(); + _modelConventions = optionsAccessor.Options.ApplicationModelConventions; } public int Order @@ -44,34 +43,35 @@ namespace Microsoft.AspNet.Mvc callNext(); } - public IEnumerable GetDescriptors() + public IEnumerable GetDescriptors() { - var assemblies = _controllerAssemblyProvider.CandidateAssemblies; - var types = assemblies.SelectMany(a => a.DefinedTypes); - var controllers = types.Where(_conventions.IsController); - var controllerDescriptors = controllers - .Select(t => _controllerDescriptorFactory.CreateControllerDescriptor(t)) - .ToArray(); + var model = BuildModel(); - return GetDescriptors(controllerDescriptors); + foreach (var convention in _modelConventions) + { + convention.OnModelCreated(model); + } + + return Build(model); } - // Internal for unit testing. - internal IEnumerable GetDescriptors(IEnumerable controllerDescriptors) + public ReflectedApplicationModel BuildModel() { - foreach (var cd in controllerDescriptors) - { - var controllerAttributes = cd.ControllerTypeInfo.GetCustomAttributes(inherit: true).ToArray(); - var globalAndControllerFilters = - controllerAttributes.OfType() - .Select(filter => new FilterDescriptor(filter, FilterScope.Controller)) - .Concat(_globalFilters) - .OrderBy(d => d, FilterDescriptorOrderComparer.Comparer) - .ToArray(); + var applicationModel = new ReflectedApplicationModel(); + applicationModel.Filters.AddRange(_globalFilters); - foreach (var methodInfo in cd.ControllerTypeInfo.AsType().GetMethods()) + var assemblies = _controllerAssemblyProvider.CandidateAssemblies; + var types = assemblies.SelectMany(a => a.DefinedTypes); + var controllerTypes = types.Where(_conventions.IsController); + + foreach (var controllerType in controllerTypes) + { + var controllerModel = new ReflectedControllerModel(controllerType); + applicationModel.Controllers.Add(controllerModel); + + foreach (var methodInfo in controllerType.AsType().GetMethods()) { - var actionInfos = _conventions.GetActions(methodInfo, cd.ControllerTypeInfo); + var actionInfos = _conventions.GetActions(methodInfo, controllerType); if (actionInfos == null) { continue; @@ -79,62 +79,140 @@ namespace Microsoft.AspNet.Mvc foreach (var actionInfo in actionInfos) { - yield return BuildDescriptor(cd, methodInfo, actionInfo, globalAndControllerFilters); + var actionModel = new ReflectedActionModel(methodInfo); + + actionModel.ActionName = actionInfo.ActionName; + actionModel.IsActionNameMatchRequired = actionInfo.RequireActionNameMatch; + actionModel.HttpMethods.AddRange(actionInfo.HttpMethods ?? Enumerable.Empty()); + + foreach (var parameter in methodInfo.GetParameters()) + { + actionModel.Parameters.Add(new ReflectedParameterModel(parameter)); + } + + controllerModel.Actions.Add(actionModel); } } } + + return applicationModel; } - private ReflectedActionDescriptor BuildDescriptor(ControllerDescriptor controllerDescriptor, - MethodInfo methodInfo, - ActionInfo actionInfo, - FilterDescriptor[] globalAndControllerFilters) + private bool HasConstraint(List constraints, string routeKey) { - var ad = new ReflectedActionDescriptor + return constraints.Any( + rc => string.Equals(rc.RouteKey, routeKey, StringComparison.OrdinalIgnoreCase)); + } + + public List Build(ReflectedApplicationModel model) + { + var actions = new List(); + + var removalConstraints = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var controller in model.Controllers) { - RouteConstraints = new List + var controllerDescriptor = new ControllerDescriptor(controller.ControllerType); + foreach (var action in controller.Actions) { - new RouteDataActionConstraint("controller", controllerDescriptor.Name) - }, + var parameterDescriptors = new List(); + foreach (var parameter in action.Parameters) + { + var isFromBody = parameter.Attributes.OfType().Any(); - Name = actionInfo.ActionName, - ControllerDescriptor = controllerDescriptor, - MethodInfo = methodInfo, - }; + parameterDescriptors.Add(new ParameterDescriptor() + { + Name = parameter.ParameterName, + IsOptional = parameter.IsOptional, - var httpMethods = actionInfo.HttpMethods; - if (httpMethods != null && httpMethods.Length > 0) + ParameterBindingInfo = isFromBody + ? null + : new ParameterBindingInfo( + parameter.ParameterName, + parameter.ParameterInfo.ParameterType), + + BodyParameterInfo = isFromBody + ? new BodyParameterInfo(parameter.ParameterInfo.ParameterType) + : null + }); + } + + var actionDescriptor = new ReflectedActionDescriptor() + { + Name = action.ActionName, + ControllerDescriptor = controllerDescriptor, + MethodInfo = action.ActionMethod, + Parameters = new List(), + RouteConstraints = new List(), + }; + + var httpMethods = action.HttpMethods; + if (httpMethods != null && httpMethods.Count > 0) + { + actionDescriptor.MethodConstraints = new List() + { + new HttpMethodConstraint(httpMethods) + }; + } + + actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( + "controller", + controller.ControllerName)); + + if (action.IsActionNameMatchRequired) + { + actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( + "action", + action.ActionName)); + } + else + { + actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( + "action", + RouteKeyHandling.DenyKey)); + } + + foreach (var constraintAttribute in controller.RouteConstraints) + { + if (constraintAttribute.BlockNonAttributedActions) + { + removalConstraints.Add(constraintAttribute.RouteKey); + } + + // Skip duplicates + if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey)) + { + actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( + constraintAttribute.RouteKey, + constraintAttribute.RouteValue)); + } + } + + actionDescriptor.FilterDescriptors = + action.Filters.Select(f => new FilterDescriptor(f, FilterScope.Action)) + .Concat(controller.Filters.Select(f => new FilterDescriptor(f, FilterScope.Controller))) + .Concat(model.Filters.Select(f => new FilterDescriptor(f, FilterScope.Global))) + .OrderBy(d => d, FilterDescriptorOrderComparer.Comparer) + .ToList(); + + actions.Add(actionDescriptor); + } + } + + foreach (var actionDescriptor in actions) { - ad.MethodConstraints = new List + foreach (var key in removalConstraints) { - new HttpMethodConstraint(httpMethods) - }; + if (!HasConstraint(actionDescriptor.RouteConstraints, key)) + { + actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( + key, + RouteKeyHandling.DenyKey)); + } + } } - if (actionInfo.RequireActionNameMatch) - { - ad.RouteConstraints.Add(new RouteDataActionConstraint("action", actionInfo.ActionName)); - } - else - { - ad.RouteConstraints.Add(new RouteDataActionConstraint("action", RouteKeyHandling.DenyKey)); - } - - ad.Parameters = methodInfo.GetParameters() - .Select(p => _parameterDescriptorFactory.GetDescriptor(p)) - .ToList(); - - var attributes = methodInfo.GetCustomAttributes(inherit: true).ToArray(); - - var filtersFromAction = attributes - .OfType() - .Select(filter => new FilterDescriptor(filter, FilterScope.Action)); - - ad.FilterDescriptors = filtersFromAction.Concat(globalAndControllerFilters) - .OrderBy(d => d, FilterDescriptorOrderComparer.Comparer) - .ToList(); - - return ad; + return actions; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/IReflectedApplicationModelConvention.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/IReflectedApplicationModelConvention.cs new file mode 100644 index 0000000000..9e3345f963 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/IReflectedApplicationModelConvention.cs @@ -0,0 +1,10 @@ +// 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.ReflectedModelBuilder +{ + public interface IReflectedApplicationModelConvention + { + void OnModelCreated([NotNull] ReflectedApplicationModel model); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedActionModel.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedActionModel.cs new file mode 100644 index 0000000000..6e99becdf5 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedActionModel.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder +{ + public class ReflectedActionModel + { + public ReflectedActionModel([NotNull] MethodInfo actionMethod) + { + ActionMethod = actionMethod; + + // CoreCLR returns IEnumerable from GetCustomAttributes - the OfType + // is needed to so that the result of ToList() is List + Attributes = actionMethod.GetCustomAttributes(inherit: true).OfType().ToList(); + + Filters = Attributes.OfType().ToList(); + + HttpMethods = new List(); + Parameters = new List(); + } + + public MethodInfo ActionMethod { get; private set; } + + public string ActionName { get; set; } + + public List Attributes { get; private set; } + + public List Filters { get; private set; } + + public List HttpMethods { get; private set; } + + public bool IsActionNameMatchRequired { get; set; } + + public List Parameters { get; private set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedApplicationModel.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedApplicationModel.cs new file mode 100644 index 0000000000..faf8d0c513 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedApplicationModel.cs @@ -0,0 +1,20 @@ +// 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.ReflectedModelBuilder +{ + public class ReflectedApplicationModel + { + public ReflectedApplicationModel() + { + Controllers = new List(); + Filters = new List(); + } + + public List Controllers { get; private set; } + + public List Filters { get; private set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedControllerModel.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedControllerModel.cs new file mode 100644 index 0000000000..c06972f175 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedControllerModel.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder +{ + public class ReflectedControllerModel + { + public ReflectedControllerModel([NotNull] TypeInfo controllerType) + { + ControllerType = controllerType; + + Actions = new List(); + + // CoreCLR returns IEnumerable from GetCustomAttributes - the OfType + // is needed to so that the result of ToList() is List + Attributes = ControllerType.GetCustomAttributes(inherit: true).OfType().ToList(); + + Filters = Attributes.OfType().ToList(); + RouteConstraints = Attributes.OfType().ToList(); + + ControllerName = controllerType.Name.EndsWith("Controller", StringComparison.Ordinal) + ? controllerType.Name.Substring(0, controllerType.Name.Length - "Controller".Length) + : controllerType.Name; + } + + public List Actions { get; private set; } + + public List Attributes { get; private set; } + + public string ControllerName { get; set; } + + public TypeInfo ControllerType { get; private set; } + + public List Filters { get; private set; } + + public List RouteConstraints { get; private set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedParameterModel.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedParameterModel.cs new file mode 100644 index 0000000000..548029dbdf --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedModelBuilder/ReflectedParameterModel.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder +{ + public class ReflectedParameterModel + { + public ReflectedParameterModel(ParameterInfo parameterInfo) + { + ParameterInfo = parameterInfo; + + // CoreCLR returns IEnumerable from GetCustomAttributes - the OfType + // is needed to so that the result of ToList() is List + Attributes = parameterInfo.GetCustomAttributes(inherit: true).OfType().ToList(); + + ParameterName = parameterInfo.Name; + IsOptional = ParameterInfo.HasDefaultValue; + } + + public List Attributes { get; private set; } + + public bool IsOptional { get; set; } + + public ParameterInfo ParameterInfo { get; private set; } + + public string ParameterName { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index c56094c67e..ca27c04c9e 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -26,10 +26,8 @@ namespace Microsoft.AspNet.Mvc var describe = new ServiceDescriber(configuration); yield return describe.Transient(); - yield return describe.Transient(); yield return describe.Scoped(); yield return describe.Transient(); - yield return describe.Transient(); yield return describe.Transient(); yield return describe.Transient(); @@ -43,8 +41,6 @@ namespace Microsoft.AspNet.Mvc yield return describe.Transient, ReflectedActionDescriptorProvider>(); - yield return describe.Transient, - ReflectedRouteConstraintsActionDescriptorProvider>(); yield return describe.Transient, ReflectedActionInvokerProvider>(); yield return describe.Singleton _controllerAssemblies = new[] { Assembly.GetExecutingAssembly() }; [Theory] @@ -217,12 +215,12 @@ namespace Microsoft.AspNet.Mvc.Core.Test { var controllerAssemblyProvider = new Mock(); controllerAssemblyProvider.SetupGet(x => x.CandidateAssemblies).Returns(_controllerAssemblies); + return new ReflectedActionDescriptorProvider( controllerAssemblyProvider.Object, actionDiscoveryConventions, - _controllerDescriptorFactory, - _parameterDescriptorFactory, - null); + null, + new MockMvcOptionsAccessor()); } private static HttpContext GetHttpContext(string httpMethod) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionSelectionConventionTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionSelectionConventionTests.cs index 9345bc1be2..649b082cb5 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionSelectionConventionTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionSelectionConventionTests.cs @@ -15,13 +15,11 @@ using Microsoft.Framework.DependencyInjection.NestedProviders; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc.Core.Test +namespace Microsoft.AspNet.Mvc.Test { public class ActionSelectionConventionTests { private DefaultActionDiscoveryConventions _actionDiscoveryConventions = new DefaultActionDiscoveryConventions(); - private IControllerDescriptorFactory _controllerDescriptorFactory = new DefaultControllerDescriptorFactory(); - private IParameterDescriptorFactory _parameterDescriptorFactory = new DefaultParameterDescriptorFactory(); private IEnumerable _controllerAssemblies = new[] { Assembly.GetExecutingAssembly() }; [Theory] @@ -188,9 +186,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test return new ReflectedActionDescriptorProvider( controllerAssemblyProvider.Object, actionDiscoveryConventions, - _controllerDescriptorFactory, - _parameterDescriptorFactory, - null); + null, + new MockMvcOptionsAccessor()); } private static HttpContext GetHttpContext(string httpMethod) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj b/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj index 3f18a38dcd..9506d5302d 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj @@ -30,6 +30,10 @@ + + + + @@ -60,6 +64,7 @@ + diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/MockMvcOptionsAccessor.cs b/test/Microsoft.AspNet.Mvc.Core.Test/MockMvcOptionsAccessor.cs new file mode 100644 index 0000000000..5b1a212300 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/MockMvcOptionsAccessor.cs @@ -0,0 +1,17 @@ +// 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.Framework.OptionsModel; + +namespace Microsoft.AspNet.Mvc.Test +{ + public class MockMvcOptionsAccessor : IOptionsAccessor + { + public MockMvcOptionsAccessor() + { + Options = new MvcOptions(); + } + + public MvcOptions Options { get; private set; } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionDescriptorProviderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionDescriptorProviderTests.cs index 7dde64e739..930466d47c 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedActionDescriptorProviderTests.cs @@ -1,21 +1,17 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc +namespace Microsoft.AspNet.Mvc.Test { public class ReflectedActionDescriptorProviderTests { - private DefaultActionDiscoveryConventions _actionDiscoveryConventions = - new DefaultActionDiscoveryConventions(); - private IControllerDescriptorFactory _controllerDescriptorFactory = new DefaultControllerDescriptorFactory(); - private IParameterDescriptorFactory _parameterDescriptorFactory = new DefaultParameterDescriptorFactory(); - [Fact] public void GetDescriptors_GetsDescriptorsOnlyForValidActionsInBaseAndDerivedController() { @@ -130,25 +126,209 @@ namespace Microsoft.AspNet.Mvc Assert.DoesNotContain("PrivateStaticMethod", actionNames); } - private IEnumerable GetActionNamesFromDerivedController() + [Fact] + public void GetDescriptors_IncludesFilters() { - return GetDescriptors(typeof(DerivedController).GetTypeInfo()).Select(a => a.Name); + // Arrange + var globalFilter = new MyFilterAttribute(1); + var provider = GetProvider(typeof(FiltersController).GetTypeInfo(), new IFilter[] + { + globalFilter, + }); + + // Act + var descriptors = provider.GetDescriptors(); + var descriptor = Assert.Single(descriptors); + + // Assert + Assert.Equal(3, descriptor.FilterDescriptors.Count); + + var filter1 = descriptor.FilterDescriptors[2]; + Assert.Same(globalFilter, filter1.Filter); + Assert.Equal(FilterScope.Global, filter1.Scope); + + var filter2 = descriptor.FilterDescriptors[1]; + Assert.Equal(2, Assert.IsType(filter2.Filter).Value); + Assert.Equal(FilterScope.Controller, filter2.Scope); + + var filter3 = descriptor.FilterDescriptors[0]; + Assert.Equal(3, Assert.IsType(filter3.Filter).Value); ; + Assert.Equal(FilterScope.Action, filter3.Scope); } - private IEnumerable GetDescriptors(TypeInfo controllerTypeInfo) + [Fact] + public void GetDescriptors_AddsHttpMethodConstraints() { - var provider = new ReflectedActionDescriptorProvider(null, - _actionDiscoveryConventions, - _controllerDescriptorFactory, - _parameterDescriptorFactory, - null); - var testControllers = new TypeInfo[] + // Arrange + var provider = GetProvider(typeof(HttpMethodController).GetTypeInfo()); + + // Act + var descriptors = provider.GetDescriptors(); + var descriptor = Assert.Single(descriptors); + + // Assert + Assert.Equal("OnlyPost", descriptor.Name); + + Assert.Single(descriptor.MethodConstraints); + Assert.Equal(new string[] { "POST" }, descriptor.MethodConstraints[0].HttpMethods); + } + + [Fact] + public void GetDescriptors_WithRouteDataConstraint_WithBlockNonAttributedActions() + { + // Arrange & Act + var descriptors = GetDescriptors( + typeof(HttpMethodController).GetTypeInfo(), + typeof(BlockNonAttributedActionsController).GetTypeInfo()).ToArray(); + + var descriptorWithoutConstraint = Assert.Single( + descriptors, + ad => ad.RouteConstraints.Any( + c => c.RouteKey == "key" && c.KeyHandling == RouteKeyHandling.DenyKey)); + + var descriptorWithConstraint = Assert.Single( + descriptors, + ad => ad.RouteConstraints.Any( + c => + c.KeyHandling == RouteKeyHandling.RequireKey && + c.RouteKey == "key" && + c.RouteValue == "value")); + + // Assert + Assert.Equal(2, descriptors.Length); + + Assert.Equal(3, descriptorWithConstraint.RouteConstraints.Count); + Assert.Single( + descriptorWithConstraint.RouteConstraints, + c => + c.RouteKey == "controller" && + c.RouteValue == "BlockNonAttributedActions"); + Assert.Single( + descriptorWithConstraint.RouteConstraints, + c => + c.RouteKey == "action" && + c.RouteValue == "Edit"); + + Assert.Equal(3, descriptorWithoutConstraint.RouteConstraints.Count); + Assert.Single( + descriptorWithoutConstraint.RouteConstraints, + c => + c.RouteKey == "controller" && + c.RouteValue == "HttpMethod"); + Assert.Single( + descriptorWithoutConstraint.RouteConstraints, + c => + c.RouteKey == "action" && + c.RouteValue == "OnlyPost"); + } + + [Fact] + public void GetDescriptors_WithRouteDataConstraint_WithoutBlockNonAttributedActions() + { + // Arrange & Act + var descriptors = GetDescriptors( + typeof(HttpMethodController).GetTypeInfo(), + typeof(DontBlockNonAttributedActionsController).GetTypeInfo()).ToArray(); + + var descriptorWithConstraint = Assert.Single( + descriptors, + ad => ad.RouteConstraints.Any( + c => + c.KeyHandling == RouteKeyHandling.RequireKey && + c.RouteKey == "key" && + c.RouteValue == "value")); + + var descriptorWithoutConstraint = Assert.Single( + descriptors, + ad => !ad.RouteConstraints.Any(c => c.RouteKey == "key")); + + // Assert + Assert.Equal(2, descriptors.Length); + + Assert.Equal(3, descriptorWithConstraint.RouteConstraints.Count); + Assert.Single( + descriptorWithConstraint.RouteConstraints, + c => + c.RouteKey == "controller" && + c.RouteValue == "DontBlockNonAttributedActions"); + Assert.Single( + descriptorWithConstraint.RouteConstraints, + c => + c.RouteKey == "action" && + c.RouteValue == "Create"); + + Assert.Equal(2, descriptorWithoutConstraint.RouteConstraints.Count); + Assert.Single( + descriptorWithoutConstraint.RouteConstraints, + c => + c.RouteKey == "controller" && + c.RouteValue == "HttpMethod"); + Assert.Single( + descriptorWithoutConstraint.RouteConstraints, + c => + c.RouteKey == "action" && + c.RouteValue == "OnlyPost"); + } + + [Fact] + public void BuildModel_IncludesGlobalFilters() + { + // Arrange + var filter = new MyFilterAttribute(1); + var provider = GetProvider(typeof(BaseController).GetTypeInfo(), new IFilter[] { - controllerTypeInfo, - }; - var controllerDescriptors = testControllers - .Select(t => _controllerDescriptorFactory.CreateControllerDescriptor(t)); - return provider.GetDescriptors(controllerDescriptors); + filter, + }); + + // Act + var model = provider.BuildModel(); + + // Assert + var filters = model.Filters; + Assert.Same(filter, Assert.Single(filters)); + } + + private IEnumerable GetActionNamesFromDerivedController() + { + return GetDescriptors(typeof(DerivedController).GetTypeInfo()).Select(a => a.Name).ToArray(); + } + + private ReflectedActionDescriptorProvider GetProvider( + TypeInfo controllerTypeInfo, + IEnumerable filters = null) + { + var assemblyProvider = new Mock(); + assemblyProvider + .SetupGet(ap => ap.CandidateAssemblies) + .Returns(new Assembly[] { controllerTypeInfo.Assembly }); + + var conventions = new StaticActionDiscoveryConventions(controllerTypeInfo); + + var provider = new ReflectedActionDescriptorProvider( + assemblyProvider.Object, + conventions, + filters, + new MockMvcOptionsAccessor()); + + return provider; + } + + private IEnumerable GetDescriptors(params TypeInfo[] controllerTypeInfos) + { + var assemblyProvider = new Mock(); + assemblyProvider + .SetupGet(ap => ap.CandidateAssemblies) + .Returns(controllerTypeInfos.Select(cti => cti.Assembly).Distinct()); + + var conventions = new StaticActionDiscoveryConventions(controllerTypeInfos); + + var provider = new ReflectedActionDescriptorProvider( + assemblyProvider.Object, + conventions, + null, + new MockMvcOptionsAccessor()); + + return provider.GetDescriptors(); } private class DerivedController : BaseController @@ -218,5 +398,56 @@ namespace Microsoft.AspNet.Mvc return base.Redirect(url + "#RedirectOverride"); } } + + [MyFilter(2)] + private class FiltersController + { + [MyFilter(3)] + public void FilterAction() + { + } + } + + private class MyFilterAttribute : Attribute, IFilter + { + public MyFilterAttribute(int value) + { + Value = value; + } + + public int Value { get; private set; } + } + + private class HttpMethodController + { + [HttpPost] + public void OnlyPost() + { + } + } + + [MyRouteConstraintAttribute(blockNonAttributedActions: true)] + private class BlockNonAttributedActionsController + { + public void Edit() + { + } + } + + [MyRouteConstraintAttribute(blockNonAttributedActions: false)] + private class DontBlockNonAttributedActionsController + { + public void Create() + { + } + } + + private class MyRouteConstraintAttribute : RouteConstraintAttribute + { + public MyRouteConstraintAttribute(bool blockNonAttributedActions) + : base("key", "value", blockNonAttributedActions) + { + } + } } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedModelBuilder/ReflectedActionModelTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedModelBuilder/ReflectedActionModelTests.cs new file mode 100644 index 0000000000..358fd4323f --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedModelBuilder/ReflectedActionModelTests.cs @@ -0,0 +1,57 @@ +// 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(2, model.Attributes.Count); + Assert.Single(model.Attributes, a => a is MyFilterAttribute); + Assert.Single(model.Attributes, a => a is MyOtherAttribute); + } + + [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(model.Filters[0]); + } + + private class BlogController + { + [MyOther] + [MyFilter] + public void Edit() + { + } + } + + private class MyFilterAttribute : Attribute, IFilter + { + } + + private class MyOtherAttribute : Attribute + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedModelBuilder/ReflectedControllerModelTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedModelBuilder/ReflectedControllerModelTests.cs new file mode 100644 index 0000000000..bca2dfc73e --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedModelBuilder/ReflectedControllerModelTests.cs @@ -0,0 +1,110 @@ +// 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.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(3, 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); + } + + [Fact] + public void ReflectedControllerModel_PopulatesFilters() + { + // Arrange + var controllerType = typeof(BlogController); + + // Act + var model = new ReflectedControllerModel(controllerType.GetTypeInfo()); + + // Assert + Assert.Single(model.Filters); + Assert.IsType(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(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); + } + + [MyOther] + [MyFilter] + [MyRouteConstraint] + private class BlogController + { + } + + private class Store + { + } + + private class MyRouteConstraintAttribute : RouteConstraintAttribute + { + public MyRouteConstraintAttribute() + : base("MyRouteConstraint", "MyRouteConstraint", false) + { + } + } + + private class MyFilterAttribute : Attribute, IFilter + { + } + + private class MyOtherAttribute : Attribute + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedModelBuilder/ReflectedParameterModelTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedModelBuilder/ReflectedParameterModelTests.cs new file mode 100644 index 0000000000..0c04a8f401 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ReflectedModelBuilder/ReflectedParameterModelTests.cs @@ -0,0 +1,64 @@ +// 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 + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/StaticActionDiscoveryConventions.cs b/test/Microsoft.AspNet.Mvc.Core.Test/StaticActionDiscoveryConventions.cs new file mode 100644 index 0000000000..4d173daefc --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/StaticActionDiscoveryConventions.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Microsoft.AspNet.Mvc.Test +{ + /// + /// An implementation of DefaultActionDiscoveryConventions that only allows controllers + /// from a fixed set of types. + /// + public class StaticActionDiscoveryConventions : DefaultActionDiscoveryConventions + { + public StaticActionDiscoveryConventions(params TypeInfo[] controllerTypes) + { + ControllerTypes = new List(controllerTypes ?? Enumerable.Empty()); + } + + public List ControllerTypes { get; private set; } + + public override bool IsController([NotNull]TypeInfo typeInfo) + { + return ControllerTypes.Contains(typeInfo); + } + } +} \ No newline at end of file