// Copyright (c) .NET Foundation. 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; using System.ComponentModel; using System.Diagnostics; using System.Reflection; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Mvc.ModelBinding { /// /// A metadata representation of a model type, property or parameter. /// [DebuggerDisplay("{DebuggerToString(),nq}")] public abstract class ModelMetadata : IEquatable, IModelMetadataProvider { /// /// The default value of . /// public static readonly int DefaultOrder = 10000; private int? _hashCode; /// /// Creates a new . /// /// The . protected ModelMetadata(ModelMetadataIdentity identity) { Identity = identity; InitializeTypeInformation(); } /// /// Gets the type containing the property if this metadata is for a property; otherwise. /// public Type ContainerType => Identity.ContainerType; /// /// Gets the metadata for if this metadata is for a property; /// otherwise. /// public virtual ModelMetadata ContainerMetadata { get { throw new NotImplementedException(); } } /// /// Gets a value indicating the kind of metadata element represented by the current instance. /// public ModelMetadataKind MetadataKind => Identity.MetadataKind; /// /// Gets the model type represented by the current instance. /// public Type ModelType => Identity.ModelType; /// /// Gets the name of the parameter or property if this metadata is for a parameter or property; /// otherwise i.e. if this is the metadata for a type. /// public string Name => Identity.Name; /// /// Gets the name of the parameter if this metadata is for a parameter; otherwise. /// public string ParameterName => MetadataKind == ModelMetadataKind.Parameter ? Identity.Name : null; /// /// Gets the name of the property if this metadata is for a property; otherwise. /// public string PropertyName => MetadataKind == ModelMetadataKind.Property ? Identity.Name : null; /// /// Gets the key for the current instance. /// protected ModelMetadataIdentity Identity { get; } /// /// Gets a collection of additional information about the model. /// public abstract IReadOnlyDictionary AdditionalValues { get; } /// /// Gets the collection of instances for the model's properties. /// public abstract ModelPropertyCollection Properties { get; } /// /// Gets the name of a model if specified explicitly using . /// public abstract string BinderModelName { get; } /// /// Gets the of an of a model if specified explicitly using /// . /// public abstract Type BinderType { get; } /// /// Gets a binder metadata for this model. /// public abstract BindingSource BindingSource { get; } /// /// Gets a value indicating whether or not to convert an empty string value or one containing only whitespace /// characters to null when representing a model as text. /// public abstract bool ConvertEmptyStringToNull { get; } /// /// Gets the name of the model's datatype. Overrides in some /// display scenarios. /// /// null unless set manually or through additional metadata e.g. attributes. public abstract string DataTypeName { get; } /// /// Gets the description of the model. /// public abstract string Description { get; } /// /// Gets the format string (see https://msdn.microsoft.com/en-us/library/txafckwd.aspx) used to display the /// model. /// public abstract string DisplayFormatString { get; } /// /// Gets the display name of the model. /// public abstract string DisplayName { get; } /// /// Gets the format string (see https://msdn.microsoft.com/en-us/library/txafckwd.aspx) used to edit the model. /// public abstract string EditFormatString { get; } /// /// Gets the for elements of if that /// implements . /// /// /// for T if implements /// . for object if /// implements but not . null otherwise i.e. when /// is false. /// public abstract ModelMetadata ElementMetadata { get; } /// /// Gets the ordered and grouped display names and values of all values in /// . /// /// /// An of of mappings between /// field groups, names and values. null if is false. /// public abstract IEnumerable> EnumGroupedDisplayNamesAndValues { get; } /// /// Gets the names and values of all values in . /// /// /// An of mappings between field names /// and values. null if is false. /// public abstract IReadOnlyDictionary EnumNamesAndValues { get; } /// /// Gets a value indicating whether has a non-null, non-empty /// value different from the default for the datatype. /// public abstract bool HasNonDefaultEditFormat { get; } /// /// Gets a value indicating whether the value should be HTML-encoded. /// /// If true, value should be HTML-encoded. Default is true. public abstract bool HtmlEncode { get; } /// /// Gets a value indicating whether the "HiddenInput" display template should return /// string.Empty (not the expression value) and whether the "HiddenInput" editor template should not /// also return the expression value (together with the hidden <input> element). /// /// /// If true, also causes the default display and editor templates to return HTML /// lacking the usual per-property <div> wrapper around the associated property. Thus the default /// display template effectively skips the property and the default /// editor template returns only the hidden <input> element for the property. /// public abstract bool HideSurroundingHtml { get; } /// /// Gets a value indicating whether or not the model value can be bound by model binding. This is only /// applicable when the current instance represents a property. /// /// /// If true then the model value is considered supported by model binding and can be set /// based on provided input in the request. /// public abstract bool IsBindingAllowed { get; } /// /// Gets a value indicating whether or not the model value is required by model binding. This is only /// applicable when the current instance represents a property. /// /// /// If true then the model value is considered required by model binding and must have a value /// supplied in the request to be considered valid. /// public abstract bool IsBindingRequired { get; } /// /// Gets a value indicating whether is for an . /// /// /// true if type.IsEnum (type.GetTypeInfo().IsEnum for DNX Core 5.0) is true for /// ; false otherwise. /// public abstract bool IsEnum { get; } /// /// Gets a value indicating whether is for an with an /// associated . /// /// /// true if is true and has an /// associated ; false otherwise. /// public abstract bool IsFlagsEnum { get; } /// /// Gets a value indicating whether or not the model value is read-only. This is only applicable when /// the current instance represents a property. /// public abstract bool IsReadOnly { get; } /// /// Gets a value indicating whether or not the model value is required. This is only applicable when /// the current instance represents a property. /// /// /// /// If true then the model value is considered required by validators. /// /// /// By default an implicit System.ComponentModel.DataAnnotations.RequiredAttribute will be added /// if not present when true.. /// /// public abstract bool IsRequired { get; } /// /// Gets the instance. /// public abstract ModelBindingMessageProvider ModelBindingMessageProvider { get; } /// /// Gets a value indicating where the current metadata should be ordered relative to other properties /// in its containing type. /// /// /// For example this property is used to order items in . /// The default order is 10000. /// /// The order value of the current metadata. public abstract int Order { get; } /// /// Gets the text to display as a placeholder value for an editor. /// public abstract string Placeholder { get; } /// /// Gets the text to display when the model is null. /// public abstract string NullDisplayText { get; } /// /// Gets the , which can determine which properties /// should be model bound. /// public abstract IPropertyFilterProvider PropertyFilterProvider { get; } /// /// Gets a value that indicates whether the property should be displayed in read-only views. /// public abstract bool ShowForDisplay { get; } /// /// Gets a value that indicates whether the property should be displayed in editable views. /// public abstract bool ShowForEdit { get; } /// /// Gets a value which is the name of the property used to display the model. /// public abstract string SimpleDisplayProperty { get; } /// /// Gets a string used by the templating system to discover display-templates and editor-templates. /// public abstract string TemplateHint { get; } /// /// Gets an implementation that indicates whether this model should be /// validated. If null, properties with this are validated. /// /// Defaults to null. public virtual IPropertyValidationFilter PropertyValidationFilter => null; /// /// Gets a value that indicates whether properties or elements of the model should be validated. /// public abstract bool ValidateChildren { get; } /// /// Gets a value that indicates if the model, or one of it's properties, or elements has associatated validators. /// /// /// When , validation can be assume that the model is valid () without /// inspecting the object graph. /// public virtual bool? HasValidators { get; } /// /// Gets a collection of metadata items for validators. /// public abstract IReadOnlyList ValidatorMetadata { get; } /// /// Gets the for elements of if that /// implements . /// public Type ElementType { get; private set; } /// /// Gets a value indicating whether is a complex type. /// /// /// A complex type is defined as a without a that can convert /// from . Most POCO and types are therefore complex. Most, if /// not all, BCL value types are simple types. /// public bool IsComplexType { get; private set; } /// /// Gets a value indicating whether or not is a . /// public bool IsNullableValueType { get; private set; } /// /// Gets a value indicating whether or not is a collection type. /// /// /// A collection type is defined as a which is assignable to . /// public bool IsCollectionType { get; private set; } /// /// Gets a value indicating whether or not is an enumerable type. /// /// /// An enumerable type is defined as a which is assignable to /// , and is not a . /// public bool IsEnumerableType { get; private set; } /// /// Gets a value indicating whether or not allows null values. /// public bool IsReferenceOrNullableType { get; private set; } /// /// Gets the underlying type argument if inherits from . /// Otherwise gets . /// /// /// Identical to unless is true. /// public Type UnderlyingOrModelType { get; private set; } /// /// Gets a property getter delegate to get the property value from a model object. /// public abstract Func PropertyGetter { get; } /// /// Gets a property setter delegate to set the property value on a model object. /// public abstract Action PropertySetter { get; } /// /// Gets a display name for the model. /// /// /// will return the first of the following expressions which has a /// non- value: , , or ModelType.Name. /// /// The display name. public string GetDisplayName() { return DisplayName ?? Name ?? ModelType.Name; } /// public bool Equals(ModelMetadata other) { if (object.ReferenceEquals(this, other)) { return true; } if (other == null) { return false; } else { return Identity.Equals(other.Identity); } } /// public override bool Equals(object obj) { return Equals(obj as ModelMetadata); } /// public override int GetHashCode() { // Normally caching the hashcode would be dangerous, but Identity is deeply immutable so this is safe. if (_hashCode == null) { _hashCode = Identity.GetHashCode(); } return _hashCode.Value; } private void InitializeTypeInformation() { Debug.Assert(ModelType != null); IsComplexType = !TypeDescriptor.GetConverter(ModelType).CanConvertFrom(typeof(string)); IsNullableValueType = Nullable.GetUnderlyingType(ModelType) != null; IsReferenceOrNullableType = !ModelType.GetTypeInfo().IsValueType || IsNullableValueType; UnderlyingOrModelType = Nullable.GetUnderlyingType(ModelType) ?? ModelType; var collectionType = ClosedGenericMatcher.ExtractGenericInterface(ModelType, typeof(ICollection<>)); IsCollectionType = collectionType != null; if (ModelType == typeof(string) || !typeof(IEnumerable).IsAssignableFrom(ModelType)) { // Do nothing, not Enumerable. } else if (ModelType.IsArray) { IsEnumerableType = true; ElementType = ModelType.GetElementType(); } else { IsEnumerableType = true; var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(ModelType, typeof(IEnumerable<>)); ElementType = enumerableType?.GenericTypeArguments[0]; if (ElementType == null) { // ModelType implements IEnumerable but not IEnumerable. ElementType = typeof(object); } Debug.Assert( ElementType != null, $"Unable to find element type for '{ModelType.FullName}' though IsEnumerableType is true."); } } private string DebuggerToString() { switch (MetadataKind) { case ModelMetadataKind.Parameter: return $"ModelMetadata (Parameter: '{ParameterName}' Type: '{ModelType.Name}')"; case ModelMetadataKind.Property: return $"ModelMetadata (Property: '{ContainerType.Name}.{PropertyName}' Type: '{ModelType.Name}')"; case ModelMetadataKind.Type: return $"ModelMetadata (Type: '{ModelType.Name}')"; default: return $"Unsupported MetadataKind '{MetadataKind}'."; } } /// public virtual ModelMetadata GetMetadataForType(Type modelType) { throw new NotImplementedException(); } /// public virtual IEnumerable GetMetadataForProperties(Type modelType) { throw new NotImplementedException(); } } }