File: ModelBinding\Binders\EnumTypeModelBinder.cs
Web Access
Project: src\src\Mvc\Mvc.Core\src\Microsoft.AspNetCore.Mvc.Core.csproj (Microsoft.AspNetCore.Mvc.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable enable
 
using System.Globalization;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
 
/// <summary>
/// <see cref="IModelBinder"/> implementation to bind models for types deriving from <see cref="Enum"/>.
/// </summary>
public class EnumTypeModelBinder : SimpleTypeModelBinder
{
    /// <summary>
    /// Initializes a new instance of <see cref="EnumTypeModelBinder"/>.
    /// </summary>
    /// <param name="suppressBindingUndefinedValueToEnumType">
    /// Flag to determine if binding to undefined should be suppressed or not.
    /// </param>
    /// <param name="modelType">The model type.</param>
    /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>,</param>
    /// <remarks>
    /// The <paramref name="suppressBindingUndefinedValueToEnumType"/> parameter is currently ignored.
    /// </remarks>
    public EnumTypeModelBinder(
        bool suppressBindingUndefinedValueToEnumType,
        Type modelType,
        ILoggerFactory loggerFactory)
        : base(modelType, loggerFactory)
    {
    }
 
    /// <inheritdoc/>
    protected override void CheckModel(
        ModelBindingContext bindingContext,
        ValueProviderResult valueProviderResult,
        object? model)
    {
        if (model == null)
        {
            base.CheckModel(bindingContext, valueProviderResult, model);
        }
        else if (IsDefinedInEnum(model, bindingContext))
        {
            bindingContext.Result = ModelBindingResult.Success(model);
        }
        else
        {
            bindingContext.ModelState.TryAddModelError(
                bindingContext.ModelName,
                bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueIsInvalidAccessor(
                    valueProviderResult.ToString()));
        }
    }
 
    private static bool IsDefinedInEnum(object model, ModelBindingContext bindingContext)
    {
        var modelType = bindingContext.ModelMetadata.UnderlyingOrModelType;
 
        // Check if the converted value is indeed defined on the enum as EnumTypeConverter
        // converts value to the backing type (ex: integer) and does not check if the value is defined on the enum.
        if (bindingContext.ModelMetadata.IsFlagsEnum)
        {
            // Enum.IsDefined does not work with combined flag enum values.
            // From EnumDataTypeAttribute.cs in CoreFX.
            // Examples:
            //
            // [Flags]
            // enum FlagsEnum { Value1 = 1, Value2 = 2, Value4 = 4 }
            //
            // Valid Scenarios:
            // 1. valueProviderResult="Value2,Value4", model=Value2 | Value4, underlying=6, converted=Value2, Value4
            // 2. valueProviderResult="2,4", model=Value2 | Value4, underlying=6, converted=Value2, Value4
            //
            // Invalid Scenarios:
            // 1. valueProviderResult="2,10", model=12, underlying=12, converted=12
            //
            var underlying = Convert.ChangeType(
                model,
                Enum.GetUnderlyingType(modelType),
                CultureInfo.InvariantCulture).ToString();
            var converted = model.ToString();
            return !string.Equals(underlying, converted, StringComparison.OrdinalIgnoreCase);
        }
        return Enum.IsDefined(modelType, model);
    }
}