|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable enable
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
/// <summary>
/// A default <see cref="ModelMetadata"/> implementation.
/// </summary>
public class DefaultModelMetadata : ModelMetadata
{
private readonly IModelMetadataProvider _provider;
private readonly ICompositeMetadataDetailsProvider _detailsProvider;
private readonly DefaultMetadataDetails _details;
// Default message provider for all DefaultModelMetadata instances; cloned before exposing to
// IBindingMetadataProvider instances to ensure customizations are not accidentally shared.
private readonly DefaultModelBindingMessageProvider _modelBindingMessageProvider;
private ReadOnlyDictionary<object, object>? _additionalValues;
private ModelMetadata? _elementMetadata;
private ModelMetadata? _constructorMetadata;
private bool? _isBindingRequired;
private bool? _isReadOnly;
private bool? _isRequired;
private ModelPropertyCollection? _properties;
private bool? _validateChildren;
private bool? _hasValidators;
private ReadOnlyCollection<object>? _validatorMetadata;
/// <summary>
/// Creates a new <see cref="DefaultModelMetadata"/>.
/// </summary>
/// <param name="provider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="detailsProvider">The <see cref="ICompositeMetadataDetailsProvider"/>.</param>
/// <param name="details">The <see cref="DefaultMetadataDetails"/>.</param>
public DefaultModelMetadata(
IModelMetadataProvider provider,
ICompositeMetadataDetailsProvider detailsProvider,
DefaultMetadataDetails details)
: this(provider, detailsProvider, details, new DefaultModelBindingMessageProvider())
{
}
/// <summary>
/// Creates a new <see cref="DefaultModelMetadata"/>.
/// </summary>
/// <param name="provider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="detailsProvider">The <see cref="ICompositeMetadataDetailsProvider"/>.</param>
/// <param name="details">The <see cref="DefaultMetadataDetails"/>.</param>
/// <param name="modelBindingMessageProvider">The <see cref="Metadata.DefaultModelBindingMessageProvider"/>.</param>
public DefaultModelMetadata(
IModelMetadataProvider provider,
ICompositeMetadataDetailsProvider detailsProvider,
DefaultMetadataDetails details,
DefaultModelBindingMessageProvider modelBindingMessageProvider)
: base(details.Key)
{
ArgumentNullException.ThrowIfNull(provider);
ArgumentNullException.ThrowIfNull(detailsProvider);
ArgumentNullException.ThrowIfNull(details);
ArgumentNullException.ThrowIfNull(modelBindingMessageProvider);
_provider = provider;
_detailsProvider = detailsProvider;
_details = details;
_modelBindingMessageProvider = modelBindingMessageProvider;
}
/// <summary>
/// Gets the set of attributes for the current instance.
/// </summary>
public ModelAttributes Attributes => _details.ModelAttributes;
/// <inheritdoc />
public override ModelMetadata? ContainerMetadata => _details.ContainerMetadata;
/// <summary>
/// Gets the <see cref="Metadata.BindingMetadata"/> for the current instance.
/// </summary>
/// <remarks>
/// Accessing this property will populate the <see cref="Metadata.BindingMetadata"/> if necessary.
/// </remarks>
public BindingMetadata BindingMetadata
{
get
{
if (_details.BindingMetadata == null)
{
var context = new BindingMetadataProviderContext(Identity, _details.ModelAttributes);
// Provide a unique ModelBindingMessageProvider instance so providers' customizations are per-type.
context.BindingMetadata.ModelBindingMessageProvider =
new DefaultModelBindingMessageProvider(_modelBindingMessageProvider);
_detailsProvider.CreateBindingMetadata(context);
_details.BindingMetadata = context.BindingMetadata;
}
return _details.BindingMetadata;
}
}
/// <summary>
/// Gets the <see cref="Metadata.DisplayMetadata"/> for the current instance.
/// </summary>
/// <remarks>
/// Accessing this property will populate the <see cref="Metadata.DisplayMetadata"/> if necessary.
/// </remarks>
public DisplayMetadata DisplayMetadata
{
get
{
if (_details.DisplayMetadata == null)
{
var context = new DisplayMetadataProviderContext(Identity, _details.ModelAttributes);
_detailsProvider.CreateDisplayMetadata(context);
_details.DisplayMetadata = context.DisplayMetadata;
}
return _details.DisplayMetadata;
}
}
/// <summary>
/// Gets the <see cref="Metadata.ValidationMetadata"/> for the current instance.
/// </summary>
/// <remarks>
/// Accessing this property will populate the <see cref="Metadata.ValidationMetadata"/> if necessary.
/// </remarks>
public ValidationMetadata ValidationMetadata
{
get
{
if (_details.ValidationMetadata == null)
{
var context = new ValidationMetadataProviderContext(Identity, _details.ModelAttributes);
_detailsProvider.CreateValidationMetadata(context);
_details.ValidationMetadata = context.ValidationMetadata;
}
return _details.ValidationMetadata;
}
}
/// <inheritdoc />
public override IReadOnlyDictionary<object, object> AdditionalValues
{
get
{
if (_additionalValues == null)
{
_additionalValues = new ReadOnlyDictionary<object, object>(DisplayMetadata.AdditionalValues);
}
return _additionalValues;
}
}
/// <inheritdoc />
public override BindingSource? BindingSource => BindingMetadata.BindingSource;
/// <inheritdoc />
public override string? BinderModelName => BindingMetadata.BinderModelName;
/// <inheritdoc />
public override Type? BinderType => BindingMetadata.BinderType;
/// <inheritdoc />
public override bool ConvertEmptyStringToNull => DisplayMetadata.ConvertEmptyStringToNull;
/// <inheritdoc />
public override string? DataTypeName => DisplayMetadata.DataTypeName;
/// <inheritdoc />
public override string? Description
{
get
{
if (DisplayMetadata.Description == null)
{
return null;
}
return DisplayMetadata.Description();
}
}
/// <inheritdoc />
public override string? DisplayFormatString => DisplayMetadata.DisplayFormatStringProvider();
/// <inheritdoc />
public override string? DisplayName
{
get
{
if (DisplayMetadata.DisplayName == null)
{
return null;
}
return DisplayMetadata.DisplayName();
}
}
/// <inheritdoc />
public override string? EditFormatString => DisplayMetadata.EditFormatStringProvider();
/// <inheritdoc />
public override ModelMetadata? ElementMetadata
{
get
{
if (_elementMetadata == null && ElementType != null)
{
_elementMetadata = _provider.GetMetadataForType(ElementType);
}
return _elementMetadata;
}
}
/// <inheritdoc />
public override IEnumerable<KeyValuePair<EnumGroupAndName, string>>? EnumGroupedDisplayNamesAndValues
=> DisplayMetadata.EnumGroupedDisplayNamesAndValues;
/// <inheritdoc />
public override IReadOnlyDictionary<string, string>? EnumNamesAndValues => DisplayMetadata.EnumNamesAndValues;
/// <inheritdoc />
public override bool HasNonDefaultEditFormat => DisplayMetadata.HasNonDefaultEditFormat;
/// <inheritdoc />
public override bool HideSurroundingHtml => DisplayMetadata.HideSurroundingHtml;
/// <inheritdoc />
public override bool HtmlEncode => DisplayMetadata.HtmlEncode;
/// <inheritdoc />
public override bool IsBindingAllowed
{
get
{
if (MetadataKind == ModelMetadataKind.Type)
{
return true;
}
else
{
return BindingMetadata.IsBindingAllowed;
}
}
}
/// <inheritdoc />
public override bool IsBindingRequired
{
get
{
if (!_isBindingRequired.HasValue)
{
if (MetadataKind == ModelMetadataKind.Type)
{
_isBindingRequired = false;
}
else
{
_isBindingRequired = BindingMetadata.IsBindingRequired;
}
}
return _isBindingRequired.Value;
}
}
/// <inheritdoc />
public override bool IsEnum => DisplayMetadata.IsEnum;
/// <inheritdoc />
public override bool IsFlagsEnum => DisplayMetadata.IsFlagsEnum;
/// <inheritdoc />
public override bool IsReadOnly
{
get
{
if (!_isReadOnly.HasValue)
{
if (MetadataKind == ModelMetadataKind.Type)
{
_isReadOnly = false;
}
else if (BindingMetadata.IsReadOnly.HasValue)
{
_isReadOnly = BindingMetadata.IsReadOnly;
}
else
{
_isReadOnly = _details.PropertySetter == null;
}
}
return _isReadOnly.Value;
}
}
/// <inheritdoc />
public override bool IsRequired
{
get
{
if (!_isRequired.HasValue)
{
if (ValidationMetadata.IsRequired.HasValue)
{
_isRequired = ValidationMetadata.IsRequired;
}
else
{
// Default to IsRequired = true for non-Nullable<T> value types.
_isRequired = !IsReferenceOrNullableType;
}
}
return _isRequired.Value;
}
}
/// <inheritdoc />
public override ModelBindingMessageProvider ModelBindingMessageProvider =>
BindingMetadata.ModelBindingMessageProvider!;
/// <inheritdoc />
public override string? NullDisplayText => DisplayMetadata.NullDisplayTextProvider();
/// <inheritdoc />
public override int Order => DisplayMetadata.Order;
/// <inheritdoc />
public override string? Placeholder
{
get
{
if (DisplayMetadata.Placeholder == null)
{
return null;
}
return DisplayMetadata.Placeholder();
}
}
/// <inheritdoc />
public override ModelPropertyCollection Properties
{
get
{
if (_properties == null)
{
var properties = _provider.GetMetadataForProperties(ModelType);
properties = properties.OrderBy(p => p.Order);
_properties = new ModelPropertyCollection(properties);
}
return _properties;
}
}
/// <inheritdoc />
public override ModelMetadata? BoundConstructor
{
get
{
if (BindingMetadata.BoundConstructor == null)
{
return null;
}
if (_constructorMetadata == null)
{
var modelMetadataProvider = (ModelMetadataProvider)_provider;
_constructorMetadata = modelMetadataProvider.GetMetadataForConstructor(BindingMetadata.BoundConstructor, ModelType);
}
return _constructorMetadata;
}
}
/// <inheritdoc/>
public override IReadOnlyList<ModelMetadata>? BoundConstructorParameters => _details.BoundConstructorParameters;
/// <inheritdoc />
public override IPropertyFilterProvider? PropertyFilterProvider => BindingMetadata.PropertyFilterProvider;
/// <inheritdoc />
public override bool ShowForDisplay => DisplayMetadata.ShowForDisplay;
/// <inheritdoc />
public override bool ShowForEdit => DisplayMetadata.ShowForEdit;
/// <inheritdoc />
public override string? SimpleDisplayProperty => DisplayMetadata.SimpleDisplayProperty;
/// <inheritdoc />
public override string? TemplateHint => DisplayMetadata.TemplateHint;
/// <inheritdoc />
public override IPropertyValidationFilter? PropertyValidationFilter => ValidationMetadata.PropertyValidationFilter;
/// <inheritdoc />
public override bool ValidateChildren
{
get
{
if (!_validateChildren.HasValue)
{
if (ValidationMetadata.ValidateChildren.HasValue)
{
_validateChildren = ValidationMetadata.ValidateChildren.Value;
}
else if (IsComplexType || IsEnumerableType)
{
_validateChildren = true;
}
else
{
_validateChildren = false;
}
}
return _validateChildren.Value;
}
}
/// <inheritdoc />
public override bool? HasValidators
{
get
{
if (!_hasValidators.HasValue)
{
var visited = new HashSet<DefaultModelMetadata>();
_hasValidators = CalculateHasValidators(visited, this);
}
return _hasValidators.Value;
}
}
internal override bool PropertyHasValidators => ValidationMetadata.PropertyHasValidators;
/// <inheritdoc />
internal override string? ValidationModelName => ValidationMetadata.ValidationModelName;
internal static bool CalculateHasValidators(HashSet<DefaultModelMetadata> visited, ModelMetadata metadata)
{
RuntimeHelpers.EnsureSufficientExecutionStack();
if (metadata?.GetType() != typeof(DefaultModelMetadata))
{
// The calculation is valid only for DefaultModelMetadata instances. Null, other ModelMetadata instances
// or subtypes of DefaultModelMetadata will be treated as always requiring validation.
return true;
}
var defaultModelMetadata = (DefaultModelMetadata)metadata;
if (defaultModelMetadata._hasValidators.HasValue)
{
// Return a previously calculated value if available.
return defaultModelMetadata._hasValidators.Value;
}
if (defaultModelMetadata.ValidationMetadata.HasValidators != false)
{
// Either the ModelMetadata instance has some validators (HasValidators = true) or it is non-deterministic (HasValidators = null).
// In either case, assume it has validators.
return true;
}
// Before inspecting properties or elements of a collection, ensure we do not have a cycle.
// Consider a model like so
//
// Employee { BusinessDivision Division; int Id; string Name; }
// BusinessDivision { int Id; List<Employee> Employees }
//
// If we get to the Employee element from Employee.Division.Employees, we can return false for that instance
// and allow other properties of BusinessDivision and Employee to determine if it has validators.
if (!visited.Add(defaultModelMetadata))
{
return false;
}
// We have inspected the current element. Inspect properties or elements that may contribute to this value.
if (defaultModelMetadata.IsEnumerableType)
{
if (CalculateHasValidators(visited, defaultModelMetadata.ElementMetadata!))
{
return true;
}
}
else if (defaultModelMetadata.IsComplexType)
{
var parameters = defaultModelMetadata.BoundConstructor?.BoundConstructorParameters ?? Array.Empty<ModelMetadata>();
foreach (var parameter in parameters)
{
if (CalculateHasValidators(visited, parameter))
{
return true;
}
}
foreach (var property in defaultModelMetadata.BoundProperties)
{
if (CalculateHasValidators(visited, property))
{
return true;
}
}
}
// We've come this far. The ModelMetadata does not have any validation
return false;
}
/// <inheritdoc />
public override IReadOnlyList<object> ValidatorMetadata
{
get
{
if (_validatorMetadata == null)
{
_validatorMetadata = new ReadOnlyCollection<object>(ValidationMetadata.ValidatorMetadata);
}
return _validatorMetadata;
}
}
/// <inheritdoc />
public override Func<object, object?>? PropertyGetter => _details.PropertyGetter;
/// <inheritdoc />
public override Action<object, object?>? PropertySetter => _details.PropertySetter;
/// <inheritdoc/>
public override Func<object?[], object>? BoundConstructorInvoker => _details.BoundConstructorInvoker;
internal DefaultMetadataDetails Details => _details;
/// <inheritdoc />
public override ModelMetadata GetMetadataForType(Type modelType)
{
return _provider.GetMetadataForType(modelType);
}
/// <inheritdoc />
public override IEnumerable<ModelMetadata> GetMetadataForProperties(Type modelType)
{
return _provider.GetMetadataForProperties(modelType);
}
}
|