[Perf] Cache the default values for action arguments to avoid Activator.CreateInstance per argument.
Fixes #4470
This commit is contained in:
parent
5fb36ae406
commit
61a176e478
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
object instance,
|
||||
IDictionary<string, object> 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<string, object> 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<DefaultValueAttribute>(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;
|
||||
|
|
|
|||
|
|
@ -569,7 +569,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var arguments = ControllerActionExecutor.PrepareArguments(
|
||||
_actionExecutingContext.ActionArguments,
|
||||
actionMethodInfo.GetParameters());
|
||||
_executor);
|
||||
|
||||
_logger.ActionMethodExecuting(_actionExecutingContext, arguments);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<object> 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<T>)taskAsObject;
|
||||
return CastToObject<T>(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<DefaultValueAttribute>(inherit: false);
|
||||
|
||||
if (defaultValueAttribute?.Value == null)
|
||||
{
|
||||
defaultValue = parameterInfo.ParameterType.GetTypeInfo().IsValueType
|
||||
? Activator.CreateInstance(parameterInfo.ParameterType)
|
||||
: null;
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultValue = defaultValueAttribute.Value;
|
||||
}
|
||||
}
|
||||
|
||||
_parameterDefaultValues[i] = defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<int>(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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue