Add a custom collection type for ModelMetadata.Properties

This is a cleanup PR to improve the common usage of
ModelMetadata.Properties.

We found placed in code where both .Count and the ability to index by
property name would be useful. I was able to cascade this and simplify the
ModelBindingContext as well.
This commit is contained in:
Ryan Nowak 2015-01-23 12:58:47 -08:00
parent 8a9dc991ce
commit 8399dc5f4e
11 changed files with 109 additions and 44 deletions

View File

@ -541,7 +541,6 @@ namespace Microsoft.AspNet.Mvc.Description
// Order - source: Body
//
var propertyCount = 0;
var unboundProperties = new HashSet<ModelMetadata>();
// 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)
{

View File

@ -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;

View File

@ -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.

View File

@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private Func<object> _modelAccessor;
private int _order = DefaultOrder;
private bool _isRequired;
private IEnumerable<ModelMetadata> _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<ModelMetadata> Properties
/// <summary>
/// Gets the collection of <see cref="ModelMetadata"/> instances for the model's properties.
/// </summary>
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;

View File

@ -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
{
/// <summary>
/// A read-only list of <see cref="ModelMetadata"/> objects which represent model properties.
/// </summary>
public class ModelPropertyCollection : IReadOnlyList<ModelMetadata>
{
private readonly List<ModelMetadata> _properties;
/// <summary>
/// Creates a new <see cref="ModelPropertyCollection"/>.
/// </summary>
/// <param name="properties">The properties.</param>
public ModelPropertyCollection([NotNull] IEnumerable<ModelMetadata> properties)
{
_properties = new List<ModelMetadata>(properties);
}
/// <inheritdoc />
public ModelMetadata this[int index]
{
get
{
return _properties[index];
}
}
/// <summary>
/// Gets a <see cref="ModelMetadata"/> instance for the property corresponding to <paramref name="propertyName"/>.
/// </summary>
/// <param name="propertyName">
/// The property name. Property names are compared using <see cref="StringComparison.Ordinal"/>
/// </param>
/// <returns>
/// The <see cref="ModelMetadata"/> instance for the property specified by <paramref name="propertyName"/>, or null
/// if no match can be found.
/// </returns>
public ModelMetadata this[[NotNull] string propertyName]
{
get
{
foreach (var property in _properties)
{
if (string.Equals(property.PropertyName, propertyName, StringComparison.Ordinal))
{
return property;
}
}
return null;
}
}
/// <inheritdoc />
public int Count
{
get
{
return _properties.Count;
}
}
/// <inheritdoc />
public IEnumerator<ModelMetadata> GetEnumerator()
{
return _properties.GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -17,7 +17,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private string _modelName;
private ModelStateDictionary _modelState;
private Dictionary<string, ModelMetadata> _propertyMetadata;
private ModelValidationNode _validationNode;
private Func<ModelBindingContext, string, bool> _propertyFilter;
@ -152,25 +151,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
public IValueProvider ValueProvider { get; set; }
/// <summary>
/// Gets a dictionary of property name to <see cref="ModelMetadata"/> instances for
/// <see cref="ModelMetadata.Properties"/>
/// </summary>
public IDictionary<string, ModelMetadata> PropertyMetadata
{
get
{
if (_propertyMetadata == null)
{
_propertyMetadata = ModelMetadata.Properties
.ToDictionary(m => m.PropertyName,
StringComparer.OrdinalIgnoreCase);
}
return _propertyMetadata;
}
}
public Func<ModelBindingContext, string, bool> PropertyFilter
{
get

View File

@ -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

View File

@ -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.

View File

@ -313,6 +313,7 @@ namespace Microsoft.AspNet.Mvc.Core
{
MetadataProvider = new Mock<IModelMetadataProvider>();
Metadata = new Mock<ModelMetadata>(MetadataProvider.Object, null, null, typeof(object), null);
Metadata.SetupGet(m => m.Properties).CallBase();
MetadataProvider.Setup(p => p.GetMetadataForProperties(It.IsAny<object>(), It.IsAny<Type>()))
.Returns(new ModelMetadata[0]);

View File

@ -319,6 +319,7 @@ namespace Microsoft.AspNet.Mvc.Core
{
MetadataProvider = new Mock<IModelMetadataProvider>();
Metadata = new Mock<ModelMetadata>(MetadataProvider.Object, null, null, typeof(object), null);
Metadata.SetupGet(m => m.Properties).CallBase();
MetadataProvider.Setup(p => p.GetMetadataForProperties(It.IsAny<object>(), It.IsAny<Type>()))
.Returns(new ModelMetadata[0]);

View File

@ -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;