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:
parent
8a9dc991ce
commit
8399dc5f4e
|
|
@ -541,7 +541,6 @@ namespace Microsoft.AspNet.Mvc.Description
|
||||||
// Order - source: Body
|
// Order - source: Body
|
||||||
//
|
//
|
||||||
|
|
||||||
var propertyCount = 0;
|
|
||||||
var unboundProperties = new HashSet<ModelMetadata>();
|
var unboundProperties = new HashSet<ModelMetadata>();
|
||||||
|
|
||||||
// We don't want to append the **parameter** name when building a model name.
|
// 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)
|
foreach (var propertyMetadata in modelMetadata.Properties)
|
||||||
{
|
{
|
||||||
propertyCount++;
|
|
||||||
var key = new PropertyKey(propertyMetadata, source);
|
var key = new PropertyKey(propertyMetadata, source);
|
||||||
|
|
||||||
if (Visited.Add(key))
|
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)
|
if (source == null || source == ambientSource)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -118,8 +118,7 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Try getting a property from ModelMetadata if we couldn't find an answer in ViewData
|
// Try getting a property from ModelMetadata if we couldn't find an answer in ViewData
|
||||||
var propertyMetadata =
|
var propertyMetadata = viewData.ModelMetadata.Properties[expression];
|
||||||
viewData.ModelMetadata.Properties.Where(p => p.PropertyName == expression).FirstOrDefault();
|
|
||||||
if (propertyMetadata != null)
|
if (propertyMetadata != null)
|
||||||
{
|
{
|
||||||
return propertyMetadata;
|
return propertyMetadata;
|
||||||
|
|
|
||||||
|
|
@ -315,7 +315,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
foreach (var property in properties)
|
foreach (var property in properties)
|
||||||
{
|
{
|
||||||
var propertyName = property.Name;
|
var propertyName = property.Name;
|
||||||
var propertyMetadata = bindingContext.PropertyMetadata[propertyName];
|
var propertyMetadata = bindingContext.ModelMetadata.Properties[propertyName];
|
||||||
var requiredValidator = bindingContext.OperationBindingContext
|
var requiredValidator = bindingContext.OperationBindingContext
|
||||||
.ValidatorProvider
|
.ValidatorProvider
|
||||||
.GetValidators(propertyMetadata)
|
.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
|
// 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.
|
// ensures a failure result from a required validator (if any) even for a non-nullable property.
|
||||||
// (Otherwise, propertyMetadata.Model is likely already null.)
|
// (Otherwise, propertyMetadata.Model is likely already null.)
|
||||||
var propertyMetadata = bindingContext.PropertyMetadata[missingRequiredProperty];
|
var propertyMetadata = bindingContext.ModelMetadata.Properties[missingRequiredProperty];
|
||||||
propertyMetadata.Model = null;
|
propertyMetadata.Model = null;
|
||||||
|
|
||||||
// Execute validator (if any) to get custom error message.
|
// Execute validator (if any) to get custom error message.
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
private Func<object> _modelAccessor;
|
private Func<object> _modelAccessor;
|
||||||
private int _order = DefaultOrder;
|
private int _order = DefaultOrder;
|
||||||
private bool _isRequired;
|
private bool _isRequired;
|
||||||
private IEnumerable<ModelMetadata> _properties;
|
private ModelPropertyCollection _properties;
|
||||||
private Type _realModelType;
|
private Type _realModelType;
|
||||||
private string _simpleDisplayText;
|
private string _simpleDisplayText;
|
||||||
|
|
||||||
|
|
@ -193,14 +193,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
public virtual string NullDisplayText { get; set; }
|
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
|
get
|
||||||
{
|
{
|
||||||
if (_properties == null)
|
if (_properties == null)
|
||||||
{
|
{
|
||||||
var properties = Provider.GetMetadataForProperties(Model, RealModelType);
|
var properties = Provider.GetMetadataForProperties(Model, RealModelType);
|
||||||
_properties = properties.OrderBy(m => m.Order).ToList();
|
_properties = new ModelPropertyCollection(properties.OrderBy(m => m.Order));
|
||||||
}
|
}
|
||||||
|
|
||||||
return _properties;
|
return _properties;
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
private string _modelName;
|
private string _modelName;
|
||||||
private ModelStateDictionary _modelState;
|
private ModelStateDictionary _modelState;
|
||||||
private Dictionary<string, ModelMetadata> _propertyMetadata;
|
|
||||||
private ModelValidationNode _validationNode;
|
private ModelValidationNode _validationNode;
|
||||||
private Func<ModelBindingContext, string, bool> _propertyFilter;
|
private Func<ModelBindingContext, string, bool> _propertyFilter;
|
||||||
|
|
||||||
|
|
@ -152,25 +151,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IValueProvider ValueProvider { get; set; }
|
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
|
public Func<ModelBindingContext, string, bool> PropertyFilter
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
|
|
@ -141,8 +141,8 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
|
|
||||||
var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "p1", Property2 = null };
|
var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "p1", Property2 = null };
|
||||||
var html = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
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;
|
metadata.HideSurroundingHtml = true;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
|
||||||
|
|
@ -187,8 +187,8 @@ Environment.NewLine;
|
||||||
|
|
||||||
var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "p1", Property2 = null };
|
var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "p1", Property2 = null };
|
||||||
var html = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
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;
|
metadata.HideSurroundingHtml = true;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -340,8 +340,8 @@ Environment.NewLine;
|
||||||
viewEngine.Object,
|
viewEngine.Object,
|
||||||
innerHelper => new StubbyHtmlHelper(innerHelper));
|
innerHelper => new StubbyHtmlHelper(innerHelper));
|
||||||
helper.ViewData["Property1"] = "True";
|
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;
|
metadata.DataTypeName = templateName;
|
||||||
|
|
||||||
// TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates.
|
// TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates.
|
||||||
|
|
@ -372,8 +372,8 @@ Environment.NewLine;
|
||||||
model,
|
model,
|
||||||
viewEngine.Object,
|
viewEngine.Object,
|
||||||
innerHelper => new StubbyHtmlHelper(innerHelper));
|
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;
|
metadata.DataTypeName = templateName;
|
||||||
|
|
||||||
// TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates.
|
// TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates.
|
||||||
|
|
@ -405,8 +405,8 @@ Environment.NewLine;
|
||||||
viewEngine.Object,
|
viewEngine.Object,
|
||||||
innerHelper => new StubbyHtmlHelper(innerHelper));
|
innerHelper => new StubbyHtmlHelper(innerHelper));
|
||||||
helper.ViewData["Property1"] = "True";
|
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;
|
metadata.TemplateHint = templateName;
|
||||||
|
|
||||||
// TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates.
|
// TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates.
|
||||||
|
|
@ -437,8 +437,8 @@ Environment.NewLine;
|
||||||
model,
|
model,
|
||||||
viewEngine.Object,
|
viewEngine.Object,
|
||||||
innerHelper => new StubbyHtmlHelper(innerHelper));
|
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;
|
metadata.TemplateHint = templateName;
|
||||||
|
|
||||||
// TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates.
|
// TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates.
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,7 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
{
|
{
|
||||||
MetadataProvider = new Mock<IModelMetadataProvider>();
|
MetadataProvider = new Mock<IModelMetadataProvider>();
|
||||||
Metadata = new Mock<ModelMetadata>(MetadataProvider.Object, null, null, typeof(object), null);
|
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>()))
|
MetadataProvider.Setup(p => p.GetMetadataForProperties(It.IsAny<object>(), It.IsAny<Type>()))
|
||||||
.Returns(new ModelMetadata[0]);
|
.Returns(new ModelMetadata[0]);
|
||||||
|
|
|
||||||
|
|
@ -319,6 +319,7 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
{
|
{
|
||||||
MetadataProvider = new Mock<IModelMetadataProvider>();
|
MetadataProvider = new Mock<IModelMetadataProvider>();
|
||||||
Metadata = new Mock<ModelMetadata>(MetadataProvider.Object, null, null, typeof(object), null);
|
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>()))
|
MetadataProvider.Setup(p => p.GetMetadataForProperties(It.IsAny<object>(), It.IsAny<Type>()))
|
||||||
.Returns(new ModelMetadata[0]);
|
.Returns(new ModelMetadata[0]);
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
// Arrange
|
// Arrange
|
||||||
var provider = new DataAnnotationsModelMetadataProvider();
|
var provider = new DataAnnotationsModelMetadataProvider();
|
||||||
var metadata = provider.GetMetadataForType(modelAccessor: null, modelType: typeof(ClassWithHiddenProperties));
|
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
|
// Act
|
||||||
var result = property.HideSurroundingHtml;
|
var result = property.HideSurroundingHtml;
|
||||||
|
|
@ -200,7 +200,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
// Arrange
|
// Arrange
|
||||||
var provider = new DataAnnotationsModelMetadataProvider();
|
var provider = new DataAnnotationsModelMetadataProvider();
|
||||||
var metadata = provider.GetMetadataForType(modelAccessor: null, modelType: typeof(ClassWithHiddenProperties));
|
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
|
// Act
|
||||||
var result = property.HideSurroundingHtml;
|
var result = property.HideSurroundingHtml;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue