// 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.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata { /// /// An implementation of and for /// the System.ComponentModel.DataAnnotations attribute classes. /// public class DataAnnotationsMetadataProvider : IBindingMetadataProvider, IDisplayMetadataProvider, IValidationMetadataProvider { /// public void GetBindingMetadata(BindingMetadataProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var editableAttribute = context.Attributes.OfType().FirstOrDefault(); if (editableAttribute != null) { context.BindingMetadata.IsReadOnly = !editableAttribute.AllowEdit; } } /// public void GetDisplayMetadata(DisplayMetadataProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var attributes = context.Attributes; var dataTypeAttribute = attributes.OfType().FirstOrDefault(); var displayAttribute = attributes.OfType().FirstOrDefault(); var displayColumnAttribute = attributes.OfType().FirstOrDefault(); var displayFormatAttribute = attributes.OfType().FirstOrDefault(); var hiddenInputAttribute = attributes.OfType().FirstOrDefault(); var scaffoldColumnAttribute = attributes.OfType().FirstOrDefault(); var uiHintAttribute = attributes.OfType().FirstOrDefault(); // Special case the [DisplayFormat] attribute hanging off an applied [DataType] attribute. This property is // non-null for DataType.Currency, DataType.Date, DataType.Time, and potentially custom [DataType] // subclasses. The DataType.Currency, DataType.Date, and DataType.Time [DisplayFormat] attributes have a // non-null DataFormatString and the DataType.Date and DataType.Time [DisplayFormat] attributes have // ApplyFormatInEditMode==true. if (displayFormatAttribute == null && dataTypeAttribute != null) { displayFormatAttribute = dataTypeAttribute.DisplayFormat; } var displayMetadata = context.DisplayMetadata; // ConvertEmptyStringToNull if (displayFormatAttribute != null) { displayMetadata.ConvertEmptyStringToNull = displayFormatAttribute.ConvertEmptyStringToNull; } // DataTypeName if (dataTypeAttribute != null) { displayMetadata.DataTypeName = dataTypeAttribute.GetDataTypeName(); } else if (displayFormatAttribute != null && !displayFormatAttribute.HtmlEncode) { displayMetadata.DataTypeName = DataType.Html.ToString(); } // Description if (displayAttribute != null) { displayMetadata.Description = () => displayAttribute.GetDescription(); } // DisplayFormatString if (displayFormatAttribute != null) { displayMetadata.DisplayFormatString = displayFormatAttribute.DataFormatString; } // DisplayName if (displayAttribute != null) { displayMetadata.DisplayName = () => displayAttribute.GetName(); } // EditFormatString if (displayFormatAttribute != null && displayFormatAttribute.ApplyFormatInEditMode) { displayMetadata.EditFormatString = displayFormatAttribute.DataFormatString; } // IsEnum et cetera var underlyingType = Nullable.GetUnderlyingType(context.Key.ModelType) ?? context.Key.ModelType; var underlyingTypeInfo = underlyingType.GetTypeInfo(); if (underlyingTypeInfo.IsEnum) { // IsEnum displayMetadata.IsEnum = true; // IsFlagsEnum displayMetadata.IsFlagsEnum = underlyingTypeInfo.GetCustomAttribute(inherit: false) != null; // EnumDisplayNamesAndValues and EnumNamesAndValues // // Order EnumDisplayNamesAndValues to match Enum.GetNames(). That method orders by absolute value, // then its behavior is undefined (but hopefully stable). Add to EnumNamesAndValues in same order but // Dictionary does not guarantee order will be preserved. var displayNamesAndValues = new List>(); var namesAndValues = new Dictionary(); foreach (var name in Enum.GetNames(underlyingType)) { var field = underlyingType.GetField(name); var displayName = GetDisplayName(field); var value = ((Enum)field.GetValue(obj: null)).ToString("d"); displayNamesAndValues.Add(new KeyValuePair(displayName, value)); namesAndValues.Add(name, value); } displayMetadata.EnumDisplayNamesAndValues = displayNamesAndValues; displayMetadata.EnumNamesAndValues = namesAndValues; } // HasNonDefaultEditFormat if (!string.IsNullOrEmpty(displayFormatAttribute?.DataFormatString) && displayFormatAttribute?.ApplyFormatInEditMode == true) { // Have a non-empty EditFormatString based on [DisplayFormat] from our cache. if (dataTypeAttribute == null) { // Attributes include no [DataType]; [DisplayFormat] was applied directly. displayMetadata.HasNonDefaultEditFormat = true; } else if (dataTypeAttribute.DisplayFormat != displayFormatAttribute) { // Attributes include separate [DataType] and [DisplayFormat]; [DisplayFormat] provided override. displayMetadata.HasNonDefaultEditFormat = true; } else if (dataTypeAttribute.GetType() != typeof(DataTypeAttribute)) { // Attributes include [DisplayFormat] copied from [DataType] and [DataType] was of a subclass. // Assume the [DataType] constructor used the protected DisplayFormat setter to override its // default. That is derived [DataType] provided override. displayMetadata.HasNonDefaultEditFormat = true; } } // HideSurroundingHtml if (hiddenInputAttribute != null) { displayMetadata.HideSurroundingHtml = !hiddenInputAttribute.DisplayValue; } // HtmlEncode if (displayFormatAttribute != null) { displayMetadata.HtmlEncode = displayFormatAttribute.HtmlEncode; } // NullDisplayText if (displayFormatAttribute != null) { displayMetadata.NullDisplayText = displayFormatAttribute.NullDisplayText; } // Order if (displayAttribute?.GetOrder() != null) { displayMetadata.Order = displayAttribute.GetOrder().Value; } // ShowForDisplay if (scaffoldColumnAttribute != null) { displayMetadata.ShowForDisplay = scaffoldColumnAttribute.Scaffold; } // ShowForEdit if (scaffoldColumnAttribute != null) { displayMetadata.ShowForEdit = scaffoldColumnAttribute.Scaffold; } // SimpleDisplayProperty if (displayColumnAttribute != null) { displayMetadata.SimpleDisplayProperty = displayColumnAttribute.DisplayColumn; } // TemplateHint if (uiHintAttribute != null) { displayMetadata.TemplateHint = uiHintAttribute.UIHint; } else if (hiddenInputAttribute != null) { displayMetadata.TemplateHint = "HiddenInput"; } } /// public void GetValidationMetadata(ValidationMetadataProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } // RequiredAttribute marks a property as required by validation - this means that it // must have a non-null value on the model during validation. var requiredAttribute = context.Attributes.OfType().FirstOrDefault(); if (requiredAttribute != null) { context.ValidationMetadata.IsRequired = true; } foreach (var attribute in context.Attributes.OfType()) { // If another provider has already added this attribute, do not repeat it. // This will prevent attributes like RemoteAttribute (which implement ValidationAttribute and // IClientModelValidator) to be added to the ValidationMetadata twice. // This is to ensure we do not end up with duplication validation rules on the client side. if (!context.ValidationMetadata.ValidatorMetadata.Contains(attribute)) { context.ValidationMetadata.ValidatorMetadata.Add(attribute); } } } // Return non-empty name specified in a [Display] attribute for a field, if any; field.Name otherwise. private static string GetDisplayName(FieldInfo field) { var display = field.GetCustomAttribute(inherit: false); if (display != null) { // Note [Display(Name = "")] is allowed. var name = display.GetName(); if (name != null) { return name; } } return field.Name; } } }