// 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.Extensions.Internal; namespace Microsoft.AspNetCore.Mvc.ModelBinding { /// /// A metadata representation of a model type, property or parameter. /// [DebuggerDisplay("{DebuggerToString(),nq}")] public abstract class ModelMetadata { /// /// The default value of . /// public static readonly int DefaultOrder = 10000; /// /// Creates a new . /// /// The . protected ModelMetadata(ModelMetadataIdentity identity) { Identity = identity; InitializeTypeInformation(); } /// /// Gets the container type of this metadata if it represents a property, otherwise null. /// public Type ContainerType { get { return Identity.ContainerType; } } /// /// Gets a value indicating the kind of metadata element represented by the current instance. /// public ModelMetadataKind MetadataKind { get { return Identity.MetadataKind; } } /// /// Gets the model type represented by the current instance. /// public Type ModelType { get { return Identity.ModelType; } } /// /// Gets the property name represented by the current instance. /// public string PropertyName { get { return Identity.Name; } } /// /// 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 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 composite format (see /// http://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 composite format (see /// http://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 IModelBindingMessageProvider 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 IPropertyBindingPredicateProvider PropertyBindingPredicateProvider { 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 a value that indicates whether properties or elements of the model should be validated. /// public abstract bool ValidateChildren { 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 simple type. /// /// /// A simple type is defined as a which has a /// that can convert from . /// 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-null value: DisplayName, PropertyName, ModelType.Name. /// /// The display name. public string GetDisplayName() { return DisplayName ?? PropertyName ?? ModelType.Name; } 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 && typeof(IEnumerable).IsAssignableFrom(ModelType)) { // 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() { if (Identity.MetadataKind == ModelMetadataKind.Type) { return $"ModelMetadata (Type: '{ModelType.Name}')"; } else { return $"ModelMetadata (Property: '{ContainerType.Name}.{PropertyName}' Type: '{ModelType.Name}')"; } } } }