diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionExecutor.cs index e24f948f70..965d0f8022 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionExecutor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionExecutor.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal object instance, IDictionary actionArguments) { - var orderedArguments = PrepareArguments(actionArguments, actionMethodExecutor.MethodInfo.GetParameters()); + var orderedArguments = PrepareArguments(actionArguments, actionMethodExecutor); return ExecuteAsync(actionMethodExecutor, instance, orderedArguments); } @@ -30,8 +30,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal public static object[] PrepareArguments( IDictionary actionParameters, - ParameterInfo[] declaredParameterInfos) + ObjectMethodExecutor actionMethodExecutor) { + var declaredParameterInfos = actionMethodExecutor.ActionParameters; var count = declaredParameterInfos.Length; if (count == 0) { @@ -46,26 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal if (!actionParameters.TryGetValue(parameterInfo.Name, out value)) { - if (parameterInfo.HasDefaultValue) - { - value = parameterInfo.DefaultValue; - } - else - { - var defaultValueAttribute = - parameterInfo.GetCustomAttribute(inherit: false); - - if (defaultValueAttribute?.Value == null) - { - value = parameterInfo.ParameterType.GetTypeInfo().IsValueType - ? Activator.CreateInstance(parameterInfo.ParameterType) - : null; - } - else - { - value = defaultValueAttribute.Value; - } - } + value = actionMethodExecutor.GetDefaultValueForParameter(index); } arguments[index] = value; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs index 7c2c3280f5..d699c6f6cf 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs @@ -569,7 +569,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var arguments = ControllerActionExecutor.PrepareArguments( _actionExecutingContext.ActionArguments, - actionMethodInfo.GetParameters()); + _executor); _logger.ActionMethodExecuting(_actionExecutingContext, arguments); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ObjectMethodExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ObjectMethodExecutor.cs index b9dd6d8d58..2c91270b1a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ObjectMethodExecutor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ObjectMethodExecutor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -14,6 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { public class ObjectMethodExecutor { + private object[] _parameterDefaultValues; private ActionExecutorAsync _executorAsync; private ActionExecutor _executor; @@ -31,7 +33,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal private static readonly MethodInfo _coerceMethod = ((MethodCallExpression)_coerceTaskExpression.Body).Method; - private ObjectMethodExecutor(MethodInfo methodInfo) { if (methodInfo == null) @@ -39,6 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal throw new ArgumentNullException(nameof(methodInfo)); } MethodInfo = methodInfo; + ActionParameters = methodInfo.GetParameters(); } private delegate Task ActionExecutorAsync(object target, object[] parameters); @@ -49,6 +51,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal public MethodInfo MethodInfo { get; } + public ParameterInfo[] ActionParameters { get; } + public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo) { var executor = new ObjectMethodExecutor(methodInfo); @@ -67,6 +71,18 @@ namespace Microsoft.AspNetCore.Mvc.Internal return _executor(target, parameters); } + public object GetDefaultValueForParameter(int index) + { + if (index < 0 || index > ActionParameters.Length - 1) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + EnsureParameterDefaultValues(); + + return _parameterDefaultValues[index]; + } + private static ActionExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo) { // Parameters to executor @@ -238,5 +254,43 @@ namespace Microsoft.AspNetCore.Mvc.Internal var task = (Task)taskAsObject; return CastToObject(task); } + + private void EnsureParameterDefaultValues() + { + if (_parameterDefaultValues == null) + { + var count = ActionParameters.Length; + _parameterDefaultValues = new object[count]; + + for (var i = 0; i < count; i++) + { + var parameterInfo = ActionParameters[i]; + object defaultValue; + + if (parameterInfo.HasDefaultValue) + { + defaultValue = parameterInfo.DefaultValue; + } + else + { + var defaultValueAttribute = parameterInfo + .GetCustomAttribute(inherit: false); + + if (defaultValueAttribute?.Value == null) + { + defaultValue = parameterInfo.ParameterType.GetTypeInfo().IsValueType + ? Activator.CreateInstance(parameterInfo.ParameterType) + : null; + } + else + { + defaultValue = defaultValueAttribute.Value; + } + } + + _parameterDefaultValues[i] = defaultValue; + } + } + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs index 4b5d1da067..274fd9defb 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs @@ -102,10 +102,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents using (_logger.ViewComponentScope(context)) { var method = context.ViewComponentDescriptor.MethodInfo; - var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, method.GetParameters()); - var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context); + var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, methodExecutor); + _diagnosticSource.BeforeViewComponent(context, component); _logger.ViewComponentExecuting(context, arguments); @@ -129,10 +129,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents using (_logger.ViewComponentScope(context)) { var method = context.ViewComponentDescriptor.MethodInfo; + var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context); + var arguments = ControllerActionExecutor.PrepareArguments( context.Arguments, - method.GetParameters()); - var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context); + methodExecutor); _diagnosticSource.BeforeViewComponent(context, component); _logger.ViewComponentExecuting(context, arguments); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ObjectMethodExecutorTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ObjectMethodExecutorTest.cs index f82a58d005..1115462364 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ObjectMethodExecutorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ObjectMethodExecutorTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.ComponentModel; using System.Globalization; using System.Reflection; using System.Threading.Tasks; @@ -20,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var executor = GetExecutorForMethod("ValueMethod"); var result = executor.Execute( _targetObject, - new object[] { 10 , 20 }); + new object[] { 10, 20 }); Assert.Equal(30, (int)result); } @@ -169,6 +170,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Equal(expectedException, ex.Message); } + [Theory] + [InlineData("EchoWithDefaultAttributes", new object[] { "hello", true, 10 })] + [InlineData("EchoWithDefaultValues", new object[] { "hello", true, 20 })] + [InlineData("EchoWithDefaultValuesAndAttributes", new object[] { "hello", 20 })] + [InlineData("EchoWithNoDefaultAttributesAndValues", new object[] { null, 0, false, null })] + public void GetDefaultValueForParameters_ReturnsExpectedValues(string methodName, object[] expectedValues) + { + var executor = GetExecutorForMethod(methodName); + var defaultValues = new object[expectedValues.Length]; + + for (var index = 0; index < expectedValues.Length; index++) + { + defaultValues[index] = executor.GetDefaultValueForParameter(index); + } + + Assert.Equal(expectedValues, defaultValues); + + } + private ObjectMethodExecutor GetExecutorForMethod(string methodName) { var method = typeof(TestObject).GetMethod(methodName); @@ -239,6 +259,38 @@ namespace Microsoft.AspNetCore.Mvc.Internal return new TaskOfTDerivedType(1); } + public string EchoWithDefaultAttributes( + [DefaultValue("hello")] string input1, + [DefaultValue(true)] bool input2, + [DefaultValue(10)] int input3) + { + return input1; + } + + public string EchoWithDefaultValues( + string input1 = "hello", + bool input2 = true, + int input3 = 20) + { + return input1; + } + + public string EchoWithDefaultValuesAndAttributes( + [DefaultValue("Hi")] string input1 = "hello", + [DefaultValue(10)] int input3 = 20) + { + return input1; + } + + public string EchoWithNoDefaultAttributesAndValues( + string input1, + int input2, + bool input3, + TestObject input4) + { + return input1; + } + public class TaskDerivedType : Task { public TaskDerivedType()