Optimize allocations in argument binder
This change avoids a state machine allocation and a dictionary allocation on the common case (no bound properties). Ugly? You bet. Worth it? Yeah, seems worthwhile. This is worth about 200 bytes/request - about 3% of allocated bytes in a smallish API scenario.
This commit is contained in:
parent
0a9804056e
commit
d222900662
|
|
@ -56,7 +56,8 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
||||||
nameof(ControllerContext)));
|
nameof(ControllerContext)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perf: Avoid allocating async state machines when we know there's nothing to bind.
|
// Perf: Avoid allocating async state machines where possible. We only need the state
|
||||||
|
// machine if you need to bind properties.
|
||||||
var actionDescriptor = context.ActionDescriptor;
|
var actionDescriptor = context.ActionDescriptor;
|
||||||
if (actionDescriptor.BoundProperties.Count == 0 &&
|
if (actionDescriptor.BoundProperties.Count == 0 &&
|
||||||
actionDescriptor.Parameters.Count == 0)
|
actionDescriptor.Parameters.Count == 0)
|
||||||
|
|
@ -64,33 +65,34 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
||||||
return Task.FromResult<IDictionary<string, object>>(
|
return Task.FromResult<IDictionary<string, object>>(
|
||||||
new Dictionary<string, object>(StringComparer.Ordinal));
|
new Dictionary<string, object>(StringComparer.Ordinal));
|
||||||
}
|
}
|
||||||
|
else if (actionDescriptor.BoundProperties.Count == 0)
|
||||||
|
{
|
||||||
|
var operationBindingContext = GetOperationBindingContext(context);
|
||||||
|
return PopulateArgumentsAsync(operationBindingContext, actionDescriptor.Parameters);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return BindActionArgumentsCoreAsync(
|
return BindActionArgumentsAndPropertiesCoreAsync(
|
||||||
context,
|
context,
|
||||||
controller,
|
controller,
|
||||||
actionDescriptor);
|
actionDescriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IDictionary<string, object>> BindActionArgumentsCoreAsync(
|
private async Task<IDictionary<string, object>> BindActionArgumentsAndPropertiesCoreAsync(
|
||||||
ControllerContext context,
|
ControllerContext context,
|
||||||
object controller,
|
object controller,
|
||||||
ControllerActionDescriptor actionDescriptor)
|
ControllerActionDescriptor actionDescriptor)
|
||||||
{
|
{
|
||||||
var operationBindingContext = GetOperationBindingContext(context);
|
var operationBindingContext = GetOperationBindingContext(context);
|
||||||
var controllerProperties = new Dictionary<string, object>(StringComparer.Ordinal);
|
|
||||||
await PopulateArgumentsAsync(
|
|
||||||
operationBindingContext,
|
|
||||||
controllerProperties,
|
|
||||||
actionDescriptor.BoundProperties);
|
|
||||||
var controllerType = actionDescriptor.ControllerTypeInfo.AsType();
|
|
||||||
ActivateProperties(controller, controllerType, controllerProperties);
|
|
||||||
|
|
||||||
var actionArguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
var controllerProperties = await PopulateArgumentsAsync(
|
||||||
await PopulateArgumentsAsync(
|
operationBindingContext,
|
||||||
|
actionDescriptor.BoundProperties);
|
||||||
|
ActivateProperties(actionDescriptor, controller, controllerProperties);
|
||||||
|
|
||||||
|
var actionArguments = await PopulateArgumentsAsync(
|
||||||
operationBindingContext,
|
operationBindingContext,
|
||||||
actionArguments,
|
|
||||||
actionDescriptor.Parameters);
|
actionDescriptor.Parameters);
|
||||||
return actionArguments;
|
return actionArguments;
|
||||||
}
|
}
|
||||||
|
|
@ -145,22 +147,41 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ActivateProperties(object controller, Type containerType, Dictionary<string, object> properties)
|
private void ActivateProperties(
|
||||||
|
ControllerActionDescriptor actionDescriptor,
|
||||||
|
object controller,
|
||||||
|
IDictionary<string, object> properties)
|
||||||
{
|
{
|
||||||
var propertyHelpers = PropertyHelper.GetProperties(controller);
|
var propertyHelpers = PropertyHelper.GetProperties(controller);
|
||||||
foreach (var property in properties)
|
for (var i = 0; i < actionDescriptor.BoundProperties.Count; i++)
|
||||||
{
|
{
|
||||||
var propertyHelper = propertyHelpers.First(helper =>
|
var property = actionDescriptor.BoundProperties[i];
|
||||||
string.Equals(helper.Name, property.Key, StringComparison.Ordinal));
|
|
||||||
|
PropertyHelper propertyHelper = null;
|
||||||
|
for (var j = 0; j < propertyHelpers.Length; j++)
|
||||||
|
{
|
||||||
|
if (string.Equals(propertyHelpers[j].Name, property.Name, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
propertyHelper = propertyHelpers[j];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object value;
|
||||||
|
if (propertyHelper == null || !properties.TryGetValue(property.Name, out value))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var propertyType = propertyHelper.Property.PropertyType;
|
var propertyType = propertyHelper.Property.PropertyType;
|
||||||
var metadata = _modelMetadataProvider.GetMetadataForType(propertyType);
|
var metadata = _modelMetadataProvider.GetMetadataForType(propertyType);
|
||||||
var source = property.Value;
|
|
||||||
if (propertyHelper.Property.CanWrite && propertyHelper.Property.SetMethod?.IsPublic == true)
|
if (propertyHelper.Property.CanWrite && propertyHelper.Property.SetMethod?.IsPublic == true)
|
||||||
{
|
{
|
||||||
// Handle settable property. Do not set the property to null if the type is a non-nullable type.
|
// Handle settable property. Do not set the property to null if the type is a non-nullable type.
|
||||||
if (source != null || metadata.IsReferenceOrNullableType)
|
if (value != null || metadata.IsReferenceOrNullableType)
|
||||||
{
|
{
|
||||||
propertyHelper.SetValue(controller, source);
|
propertyHelper.SetValue(controller, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -174,7 +195,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
var target = propertyHelper.GetValue(controller);
|
var target = propertyHelper.GetValue(controller);
|
||||||
if (source == null || target == null)
|
if (value == null || target == null)
|
||||||
{
|
{
|
||||||
// Nothing to do when source or target is null.
|
// Nothing to do when source or target is null.
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -189,15 +210,16 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
||||||
// Handle a read-only collection property.
|
// Handle a read-only collection property.
|
||||||
var propertyAddRange = CallPropertyAddRangeOpenGenericMethod.MakeGenericMethod(
|
var propertyAddRange = CallPropertyAddRangeOpenGenericMethod.MakeGenericMethod(
|
||||||
metadata.ElementMetadata.ModelType);
|
metadata.ElementMetadata.ModelType);
|
||||||
propertyAddRange.Invoke(obj: null, parameters: new[] { target, source });
|
propertyAddRange.Invoke(obj: null, parameters: new[] { target, value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PopulateArgumentsAsync(
|
private async Task<IDictionary<string, object>> PopulateArgumentsAsync(
|
||||||
OperationBindingContext operationContext,
|
OperationBindingContext operationContext,
|
||||||
IDictionary<string, object> arguments,
|
|
||||||
IList<ParameterDescriptor> parameterMetadata)
|
IList<ParameterDescriptor> parameterMetadata)
|
||||||
{
|
{
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
// Perf: Avoid allocations
|
// Perf: Avoid allocations
|
||||||
for (var i = 0; i < parameterMetadata.Count; i++)
|
for (var i = 0; i < parameterMetadata.Count; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -208,6 +230,8 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
||||||
arguments[parameter.Name] = modelBindingResult.Model;
|
arguments[parameter.Name] = modelBindingResult.Model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
private OperationBindingContext GetOperationBindingContext(ControllerContext context)
|
private OperationBindingContext GetOperationBindingContext(ControllerContext context)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Abstractions;
|
using Microsoft.AspNet.Mvc.Abstractions;
|
||||||
using Microsoft.AspNet.Mvc.Formatters;
|
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding.Test;
|
using Microsoft.AspNet.Mvc.ModelBinding.Test;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue