File: System\ComponentModel\DataAnnotations\EnumDataTypeAttribute.cs
Web Access
Project: src\src\libraries\System.ComponentModel.Annotations\src\System.ComponentModel.Annotations.csproj (System.ComponentModel.Annotations)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Globalization;
 
namespace System.ComponentModel.DataAnnotations
{
    [AttributeUsage(
        AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Parameter,
        AllowMultiple = false)]
    public sealed class EnumDataTypeAttribute : DataTypeAttribute
    {
        public EnumDataTypeAttribute(Type enumType)
            : base("Enumeration")
        {
            EnumType = enumType;
        }
 
        public Type EnumType { get; }
 
        public override bool IsValid(object? value)
        {
            if (EnumType == null)
            {
                throw new InvalidOperationException(SR.EnumDataTypeAttribute_TypeCannotBeNull);
            }
            if (!EnumType.IsEnum)
            {
                throw new InvalidOperationException(SR.Format(SR.EnumDataTypeAttribute_TypeNeedsToBeAnEnum, EnumType.FullName));
            }
 
            if (value == null)
            {
                return true;
            }
            var stringValue = value as string;
            if (stringValue?.Length == 0)
            {
                return true;
            }
 
            Type valueType = value.GetType();
            if (valueType.IsEnum && EnumType != valueType)
            {
                // don't match a different enum that might map to the same underlying integer
                return false;
            }
 
            if (!valueType.IsValueType && valueType != typeof(string))
            {
                // non-value types cannot be converted
                return false;
            }
 
            if (valueType == typeof(bool) ||
                valueType == typeof(float) ||
                valueType == typeof(double) ||
                valueType == typeof(decimal) ||
                valueType == typeof(char))
            {
                // non-integral types cannot be converted
                return false;
            }
 
            object convertedValue;
            if (valueType.IsEnum)
            {
                Debug.Assert(valueType == value.GetType(), "The valueType should equal the Type of the value");
                convertedValue = value;
            }
            else
            {
                try
                {
                    convertedValue = stringValue != null
                        ? Enum.Parse(EnumType, stringValue, false)
                        : Enum.ToObject(EnumType, value);
                }
                catch (ArgumentException)
                {
                    // REVIEW: is there a better way to detect this
                    return false;
                }
            }
 
            if (IsEnumTypeInFlagsMode(EnumType))
            {
                // REVIEW: this seems to be the easiest way to ensure that the value is a valid flag combination
                // If it is, the string representation of the enum value will be something like "A, B", while
                // the string representation of the underlying value will be "3". If the enum value does not
                // match a valid flag combination, then it would also be something like "3".
                string underlying = GetUnderlyingTypeValueString(EnumType, convertedValue)!;
                string? converted = convertedValue.ToString();
                return !underlying.Equals(converted);
            }
 
            return Enum.IsDefined(EnumType, convertedValue);
        }
 
        private static bool IsEnumTypeInFlagsMode(Type enumType) =>
            enumType.IsDefined(typeof(FlagsAttribute), false);
 
        private static string? GetUnderlyingTypeValueString(Type enumType, object enumValue) =>
            Convert.ChangeType(enumValue, Enum.GetUnderlyingType(enumType), CultureInfo.InvariantCulture).ToString();
    }
}