From 7178464ed2e3d16d11a8f4a992bb7d9564424759 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 24 Nov 2016 17:11:29 -0800 Subject: [PATCH] Munch on less memory when handling property metadata - #5499 - switch `foreach` to `for` and use less Linq when accessing `modelMetadata.Properties` - change backing field for `ModelExplorer.Properties` from a list to an array --- .../DefaultApiDescriptionProvider.cs | 6 +-- .../Binders/ComplexTypeModelBinder.cs | 6 ++- .../Binders/ComplexTypeModelBinderProvider.cs | 5 +- .../Internal/ModelBindingHelper.cs | 3 +- .../ModelStateDictionaryExtensions.cs | 4 +- .../ViewFeatures/ModelExplorer.cs | 51 +++++++++++++------ .../ViewFeatures/ModelExplorerExtensions.cs | 3 +- 7 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs index e46dac2f1d..54eb9a4c3a 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs @@ -623,7 +623,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer // if (modelMetadata.IsEnumerableType || !modelMetadata.IsComplexType || - !modelMetadata.Properties.Any()) + modelMetadata.Properties.Count == 0) { Context.Results.Add(CreateResult(bindingContext, source ?? ambientSource, containerName)); return; @@ -656,10 +656,10 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer newContainerName = GetName(containerName, bindingContext); } - foreach (var propertyMetadata in modelMetadata.Properties) + for (var i = 0; i < modelMetadata.Properties.Count; i++) { + var propertyMetadata = modelMetadata.Properties[i]; var key = new PropertyKey(propertyMetadata, source); - var propertyContext = ApiParameterDescriptionContext.GetContext( propertyMetadata, bindingInfo: null, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs index 3109b1e0e7..7390426bd9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs @@ -60,8 +60,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders bindingContext.Model = CreateModel(bindingContext); } - foreach (var property in bindingContext.ModelMetadata.Properties) + for (var i = 0; i < bindingContext.ModelMetadata.Properties.Count; i++) { + var property = bindingContext.ModelMetadata.Properties[i]; if (!CanBindProperty(bindingContext, property)) { continue; @@ -228,8 +229,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders // var hasBindableProperty = false; var isAnyPropertyEnabledForValueProviderBasedBinding = false; - foreach (var propertyMetadata in bindingContext.ModelMetadata.Properties) + for (var i = 0; i < bindingContext.ModelMetadata.Properties.Count; i++) { + var propertyMetadata = bindingContext.ModelMetadata.Properties[i]; if (!CanBindProperty(bindingContext, propertyMetadata)) { continue; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs index bcdff753ff..d8168f85c6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Reflection; using System.Collections.Generic; +using System.Reflection; namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders { @@ -25,8 +25,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders HasDefaultConstructor(context.Metadata.ModelType.GetTypeInfo())) { var propertyBinders = new Dictionary(); - foreach (var property in context.Metadata.Properties) + for (var i = 0; i < context.Metadata.Properties.Count; i++) { + var property = context.Metadata.Properties[i]; propertyBinders.Add(property, context.CreateBinder(property)); } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ModelBindingHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ModelBindingHelper.cs index be01d81d8c..da9789ef2c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ModelBindingHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ModelBindingHelper.cs @@ -463,8 +463,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Internal } else if (modelMetadata.IsComplexType) { - foreach (var property in modelMetadata.Properties) + for (var i = 0; i < modelMetadata.Properties.Count; i++) { + var property = modelMetadata.Properties[i]; modelState.ClearValidationState(property.BinderModelName ?? property.PropertyName); } } diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ModelStateDictionaryExtensions.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ModelStateDictionaryExtensions.cs index cecb6c30fe..7bba44132e 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ModelStateDictionaryExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ModelStateDictionaryExtensions.cs @@ -129,9 +129,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding if (string.IsNullOrEmpty(modelKey)) { var modelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(TModel)); - - foreach (var property in modelMetadata.Properties) + for (var i = 0; i < modelMetadata.Properties.Count; i++) { + var property = modelMetadata.Properties[i]; var childKey = property.BinderModelName ?? property.PropertyName; var entries = modelState.FindKeysWithPrefix(childKey).ToArray(); foreach (var entry in entries) diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorer.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorer.cs index 14d9295e95..9b38ea9d4a 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorer.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorer.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Internal; @@ -20,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures private object _model; private Func _modelAccessor; - private List _properties; + private ModelExplorer[] _properties; /// /// Creates a new . @@ -198,24 +197,38 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures /// Includes a for each property of the /// for . /// - public IEnumerable Properties + public IEnumerable Properties => PropertiesInternal; + + private ModelExplorer[] PropertiesInternal { get { if (_properties == null) { - _properties = new List(); - var metadata = GetMetadataForRuntimeType(); + var properties = metadata.Properties; + var propertyHelpers = PropertyHelper.GetProperties(ModelType); - var properties = Enumerable.Join( - metadata.Properties, - PropertyHelper.GetProperties(ModelType), - m => m.PropertyName, - ph => ph.Property.Name, - (m, ph) => CreateExplorerForProperty(m, ph)); + _properties = new ModelExplorer[properties.Count]; + for (var i = 0; i < properties.Count; i++) + { + var propertyMetadata = properties[i]; + PropertyHelper propertyHelper = null; + for (var j = 0; j < propertyHelpers.Length; j++) + { + if (string.Equals( + propertyMetadata.PropertyName, + propertyHelpers[j].Property.Name, + StringComparison.Ordinal)) + { + propertyHelper = propertyHelpers[j]; + break; + } + } - _properties.AddRange(properties); + Debug.Assert(propertyHelper != null); + _properties[i] = CreateExplorerForProperty(propertyMetadata, propertyHelper); + } } return _properties; @@ -252,10 +265,16 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures throw new ArgumentNullException(nameof(name)); } - return Properties.FirstOrDefault(p => string.Equals( - p.Metadata.PropertyName, - name, - StringComparison.Ordinal)); + for (var i = 0; i < PropertiesInternal.Length; i++) + { + var property = PropertiesInternal[i]; + if (string.Equals(name, property.Metadata.PropertyName, StringComparison.Ordinal)) + { + return property; + } + } + + return null; } /// diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorerExtensions.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorerExtensions.cs index 237329e46c..6441a9f3e7 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorerExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorerExtensions.cs @@ -2,9 +2,8 @@ // 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.Globalization; -using Microsoft.AspNetCore.Mvc.ModelBinding; +using System.Linq; namespace Microsoft.AspNetCore.Mvc.ViewFeatures {