diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs index e0c2e9f4d0..3f7e297942 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs @@ -541,7 +541,6 @@ namespace Microsoft.AspNet.Mvc.Description // Order - source: Body // - var propertyCount = 0; var unboundProperties = new HashSet(); // We don't want to append the **parameter** name when building a model name. @@ -553,7 +552,6 @@ namespace Microsoft.AspNet.Mvc.Description foreach (var propertyMetadata in modelMetadata.Properties) { - propertyCount++; var key = new PropertyKey(propertyMetadata, source); if (Visited.Add(key)) @@ -569,7 +567,7 @@ namespace Microsoft.AspNet.Mvc.Description } } - if (unboundProperties.Count == propertyCount) + if (unboundProperties.Count == modelMetadata.Properties.Count) { if (source == null || source == ambientSource) { diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Expressions/ExpressionMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Expressions/ExpressionMetadataProvider.cs index 7e05a2094d..afac7e94d6 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Expressions/ExpressionMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Expressions/ExpressionMetadataProvider.cs @@ -118,8 +118,7 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions else { // Try getting a property from ModelMetadata if we couldn't find an answer in ViewData - var propertyMetadata = - viewData.ModelMetadata.Properties.Where(p => p.PropertyName == expression).FirstOrDefault(); + var propertyMetadata = viewData.ModelMetadata.Properties[expression]; if (propertyMetadata != null) { return propertyMetadata; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs index da70d2b8eb..9101233e1a 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs @@ -315,7 +315,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding foreach (var property in properties) { var propertyName = property.Name; - var propertyMetadata = bindingContext.PropertyMetadata[propertyName]; + var propertyMetadata = bindingContext.ModelMetadata.Properties[propertyName]; var requiredValidator = bindingContext.OperationBindingContext .ValidatorProvider .GetValidators(propertyMetadata) @@ -366,7 +366,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Update Model as SetProperty() would: Place null value where validator will check for non-null. This // ensures a failure result from a required validator (if any) even for a non-nullable property. // (Otherwise, propertyMetadata.Model is likely already null.) - var propertyMetadata = bindingContext.PropertyMetadata[missingRequiredProperty]; + var propertyMetadata = bindingContext.ModelMetadata.Properties[missingRequiredProperty]; propertyMetadata.Model = null; // Execute validator (if any) to get custom error message. diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs index 23a558f5d3..69864bc684 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private Func _modelAccessor; private int _order = DefaultOrder; private bool _isRequired; - private IEnumerable _properties; + private ModelPropertyCollection _properties; private Type _realModelType; private string _simpleDisplayText; @@ -193,14 +193,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public virtual string NullDisplayText { get; set; } - public virtual IEnumerable Properties + /// + /// Gets the collection of instances for the model's properties. + /// + public virtual ModelPropertyCollection Properties { get { if (_properties == null) { var properties = Provider.GetMetadataForProperties(Model, RealModelType); - _properties = properties.OrderBy(m => m.Order).ToList(); + _properties = new ModelPropertyCollection(properties.OrderBy(m => m.Order)); } return _properties; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelPropertyCollection.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelPropertyCollection.cs new file mode 100644 index 0000000000..868d47f958 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelPropertyCollection.cs @@ -0,0 +1,83 @@ +// 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; +using System.Collections.Generic; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + /// + /// A read-only list of objects which represent model properties. + /// + public class ModelPropertyCollection : IReadOnlyList + { + private readonly List _properties; + + /// + /// Creates a new . + /// + /// The properties. + public ModelPropertyCollection([NotNull] IEnumerable properties) + { + _properties = new List(properties); + } + + /// + public ModelMetadata this[int index] + { + get + { + return _properties[index]; + } + } + + /// + /// Gets a instance for the property corresponding to . + /// + /// + /// The property name. Property names are compared using + /// + /// + /// The instance for the property specified by , or null + /// if no match can be found. + /// + public ModelMetadata this[[NotNull] string propertyName] + { + + get + { + foreach (var property in _properties) + { + if (string.Equals(property.PropertyName, propertyName, StringComparison.Ordinal)) + { + return property; + } + } + + return null; + } + } + + /// + public int Count + { + get + { + return _properties.Count; + } + } + + /// + public IEnumerator GetEnumerator() + { + return _properties.GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs index ff987e841a..98799eb181 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs @@ -17,7 +17,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private string _modelName; private ModelStateDictionary _modelState; - private Dictionary _propertyMetadata; private ModelValidationNode _validationNode; private Func _propertyFilter; @@ -152,25 +151,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// public IValueProvider ValueProvider { get; set; } - /// - /// Gets a dictionary of property name to instances for - /// - /// - public IDictionary PropertyMetadata - { - get - { - if (_propertyMetadata == null) - { - _propertyMetadata = ModelMetadata.Properties - .ToDictionary(m => m.PropertyName, - StringComparer.OrdinalIgnoreCase); - } - - return _propertyMetadata; - } - } - public Func PropertyFilter { get diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultDisplayTemplatesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultDisplayTemplatesTest.cs index 0078efd250..0e091b6dca 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultDisplayTemplatesTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultDisplayTemplatesTest.cs @@ -141,8 +141,8 @@ namespace Microsoft.AspNet.Mvc.Core var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "p1", Property2 = null }; var html = DefaultTemplatesUtilities.GetHtmlHelper(model); - var metadata = - html.ViewData.ModelMetadata.Properties.First(m => string.Equals(m.PropertyName, "Property1")); + + var metadata = html.ViewData.ModelMetadata.Properties["Property1"]; metadata.HideSurroundingHtml = true; // Act diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTests.cs index 46ad8c3e61..cff2bf9347 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTests.cs @@ -187,8 +187,8 @@ Environment.NewLine; var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "p1", Property2 = null }; var html = DefaultTemplatesUtilities.GetHtmlHelper(model); - var metadata = - html.ViewData.ModelMetadata.Properties.First(m => string.Equals(m.PropertyName, "Property1")); + + var metadata = html.ViewData.ModelMetadata.Properties["Property1"]; metadata.HideSurroundingHtml = true; // Act @@ -340,8 +340,8 @@ Environment.NewLine; viewEngine.Object, innerHelper => new StubbyHtmlHelper(innerHelper)); helper.ViewData["Property1"] = "True"; - var metadata = - helper.ViewData.ModelMetadata.Properties.First(m => string.Equals(m.PropertyName, "Property1")); + + var metadata = helper.ViewData.ModelMetadata.Properties["Property1"]; metadata.DataTypeName = templateName; // TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates. @@ -372,8 +372,8 @@ Environment.NewLine; model, viewEngine.Object, innerHelper => new StubbyHtmlHelper(innerHelper)); - var metadata = - helper.ViewData.ModelMetadata.Properties.First(m => string.Equals(m.PropertyName, "Property1")); + + var metadata = helper.ViewData.ModelMetadata.Properties["Property1"]; metadata.DataTypeName = templateName; // TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates. @@ -405,8 +405,8 @@ Environment.NewLine; viewEngine.Object, innerHelper => new StubbyHtmlHelper(innerHelper)); helper.ViewData["Property1"] = "True"; - var metadata = - helper.ViewData.ModelMetadata.Properties.First(m => string.Equals(m.PropertyName, "Property1")); + + var metadata = helper.ViewData.ModelMetadata.Properties["Property1"]; metadata.TemplateHint = templateName; // TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates. @@ -437,8 +437,8 @@ Environment.NewLine; model, viewEngine.Object, innerHelper => new StubbyHtmlHelper(innerHelper)); - var metadata = - helper.ViewData.ModelMetadata.Properties.First(m => string.Equals(m.PropertyName, "Property1")); + + var metadata = helper.ViewData.ModelMetadata.Properties["Property1"]; metadata.TemplateHint = templateName; // TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates. diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs index 87bcb87cf0..4764ebaae9 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs @@ -313,6 +313,7 @@ namespace Microsoft.AspNet.Mvc.Core { MetadataProvider = new Mock(); Metadata = new Mock(MetadataProvider.Object, null, null, typeof(object), null); + Metadata.SetupGet(m => m.Properties).CallBase(); MetadataProvider.Setup(p => p.GetMetadataForProperties(It.IsAny(), It.IsAny())) .Returns(new ModelMetadata[0]); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperLabelExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperLabelExtensionsTest.cs index 50c2511417..6dfb11cf84 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperLabelExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperLabelExtensionsTest.cs @@ -319,6 +319,7 @@ namespace Microsoft.AspNet.Mvc.Core { MetadataProvider = new Mock(); Metadata = new Mock(MetadataProvider.Object, null, null, typeof(object), null); + Metadata.SetupGet(m => m.Properties).CallBase(); MetadataProvider.Setup(p => p.GetMetadataForProperties(It.IsAny(), It.IsAny())) .Returns(new ModelMetadata[0]); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs index 229271766c..e3a0b9c799 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataProviderTest.cs @@ -185,7 +185,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Arrange var provider = new DataAnnotationsModelMetadataProvider(); var metadata = provider.GetMetadataForType(modelAccessor: null, modelType: typeof(ClassWithHiddenProperties)); - var property = metadata.Properties.First(m => string.Equals("DirectlyHidden", m.PropertyName)); + var property = metadata.Properties["DirectlyHidden"]; // Act var result = property.HideSurroundingHtml; @@ -200,7 +200,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Arrange var provider = new DataAnnotationsModelMetadataProvider(); var metadata = provider.GetMetadataForType(modelAccessor: null, modelType: typeof(ClassWithHiddenProperties)); - var property = metadata.Properties.First(m => string.Equals("OfHiddenType", m.PropertyName)); + var property = metadata.Properties["OfHiddenType"]; // Act var result = property.HideSurroundingHtml;