aspnetcore/src/Microsoft.AspNet.Mvc.DataAn.../DataAnnotationsMetadataProv...

262 lines
11 KiB
C#

// 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
{
/// <summary>
/// An implementation of <see cref="IBindingMetadataProvider"/> and <see cref="IDisplayMetadataProvider"/> for
/// the System.ComponentModel.DataAnnotations attribute classes.
/// </summary>
public class DataAnnotationsMetadataProvider :
IBindingMetadataProvider,
IDisplayMetadataProvider,
IValidationMetadataProvider
{
/// <inheritdoc />
public void GetBindingMetadata(BindingMetadataProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var editableAttribute = context.Attributes.OfType<EditableAttribute>().FirstOrDefault();
if (editableAttribute != null)
{
context.BindingMetadata.IsReadOnly = !editableAttribute.AllowEdit;
}
}
/// <inheritdoc />
public void GetDisplayMetadata(DisplayMetadataProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var attributes = context.Attributes;
var dataTypeAttribute = attributes.OfType<DataTypeAttribute>().FirstOrDefault();
var displayAttribute = attributes.OfType<DisplayAttribute>().FirstOrDefault();
var displayColumnAttribute = attributes.OfType<DisplayColumnAttribute>().FirstOrDefault();
var displayFormatAttribute = attributes.OfType<DisplayFormatAttribute>().FirstOrDefault();
var hiddenInputAttribute = attributes.OfType<HiddenInputAttribute>().FirstOrDefault();
var scaffoldColumnAttribute = attributes.OfType<ScaffoldColumnAttribute>().FirstOrDefault();
var uiHintAttribute = attributes.OfType<UIHintAttribute>().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<FlagsAttribute>(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<KeyValuePair<string, string>>();
var namesAndValues = new Dictionary<string, string>();
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<string, string>(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";
}
}
/// <inheritdoc />
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<RequiredAttribute>().FirstOrDefault();
if (requiredAttribute != null)
{
context.ValidationMetadata.IsRequired = true;
}
foreach (var attribute in context.Attributes.OfType<ValidationAttribute>())
{
// 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<DisplayAttribute>(inherit: false);
if (display != null)
{
// Note [Display(Name = "")] is allowed.
var name = display.GetName();
if (name != null)
{
return name;
}
}
return field.Name;
}
}
}