|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
namespace Microsoft.AspNetCore.Mvc.ModelBinding;
public class EnumTypeModelBinderTest
{
[Theory]
[InlineData(typeof(IntEnum?))]
[InlineData(typeof(FlagsEnum?))]
public async Task BindModel_SetsModel_ForEmptyValue_AndNullableEnumTypes(Type modelType)
{
// Arrange
var (bindingContext, binder) = GetBinderAndContext(modelType, valueProviderValue: "");
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(bindingContext.Result.IsModelSet);
Assert.Null(bindingContext.Result.Model);
}
[Theory]
[InlineData(typeof(IntEnum))]
[InlineData(typeof(FlagsEnum))]
public async Task BindModel_AddsErrorToModelState_ForEmptyValue_AndNonNullableEnumTypes(Type modelType)
{
// Arrange
var message = "The value '' is invalid.";
var (bindingContext, binder) = GetBinderAndContext(modelType, valueProviderValue: "");
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.False(bindingContext.Result.IsModelSet);
Assert.Null(bindingContext.Result.Model);
Assert.False(bindingContext.ModelState.IsValid);
var error = Assert.Single(bindingContext.ModelState["theModelName"].Errors);
Assert.Equal(message, error.ErrorMessage);
}
[Theory]
[InlineData("Value1")]
[InlineData("1")]
public async Task BindModel_BindsEnumModels_ForValuesInArray(string enumValue)
{
// Arrange
var modelType = typeof(IntEnum);
var (bindingContext, binder) = GetBinderAndContext(
modelType,
valueProviderValue: new object[] { enumValue });
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(bindingContext.Result.IsModelSet);
var boundModel = Assert.IsType<IntEnum>(bindingContext.Result.Model);
Assert.Equal(IntEnum.Value1, boundModel);
}
[Theory]
[InlineData("1")]
[InlineData("8, 1")]
[InlineData("Value2, Value8")]
[InlineData("value8,value4,value2,value1")]
public async Task BindModel_BindsTo_NonNullableFlagsEnumType(string flagsEnumValue)
{
// Arrange
var modelType = typeof(FlagsEnum);
var enumConverter = TypeDescriptor.GetConverter(modelType);
var expected = enumConverter.ConvertFrom(flagsEnumValue).ToString();
var (bindingContext, binder) = GetBinderAndContext(
modelType,
valueProviderValue: new object[] { flagsEnumValue });
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(bindingContext.Result.IsModelSet);
var boundModel = Assert.IsType<FlagsEnum>(bindingContext.Result.Model);
Assert.Equal(expected, boundModel.ToString());
}
[Theory]
[InlineData("1")]
[InlineData("8, 1")]
[InlineData("Value2, Value8")]
[InlineData("value8,value4,value2,value1")]
public async Task BindModel_BindsTo_NullableFlagsEnumType(string flagsEnumValue)
{
// Arrange
var modelType = typeof(FlagsEnum?);
var enumConverter = TypeDescriptor.GetConverter(GetUnderlyingType(modelType));
var expected = enumConverter.ConvertFrom(flagsEnumValue).ToString();
var (bindingContext, binder) = GetBinderAndContext(
modelType,
valueProviderValue: new object[] { flagsEnumValue });
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(bindingContext.Result.IsModelSet);
var boundModel = Assert.IsType<FlagsEnum>(bindingContext.Result.Model);
Assert.Equal(expected, boundModel.ToString());
}
[Theory]
[InlineData(typeof(IntEnum), "")]
[InlineData(typeof(IntEnum), "3")]
[InlineData(typeof(FlagsEnum), "19")]
[InlineData(typeof(FlagsEnum), "0")]
[InlineData(typeof(FlagsEnum), "1, 16")]
// These two values look like big integers but are treated as two separate enum values that are
// or'd together.
[InlineData(typeof(FlagsEnum), "32,015")]
[InlineData(typeof(FlagsEnum), "32,128")]
[InlineData(typeof(IntEnum?), "3")]
[InlineData(typeof(FlagsEnum?), "19")]
[InlineData(typeof(FlagsEnum?), "0")]
[InlineData(typeof(FlagsEnum?), "1, 16")]
// These two values look like big integers but are treated as two separate enum values that are
// or'd together.
[InlineData(typeof(FlagsEnum?), "32,015")]
[InlineData(typeof(FlagsEnum?), "32,128")]
public async Task BindModel_AddsErrorToModelState_ForInvalidEnumValues(Type modelType, string suppliedValue)
{
// Arrange
var message = $"The value '{suppliedValue}' is invalid.";
var (bindingContext, binder) = GetBinderAndContext(
modelType,
valueProviderValue: new object[] { suppliedValue });
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.False(bindingContext.Result.IsModelSet);
Assert.Null(bindingContext.Result.Model);
Assert.False(bindingContext.ModelState.IsValid);
var error = Assert.Single(bindingContext.ModelState["theModelName"].Errors);
Assert.Equal(message, error.ErrorMessage);
}
[Theory]
[InlineData("8, 1")]
[InlineData("Value2, Value8")]
[InlineData("value8,value4,value2,value1")]
public async Task BindModel_BindsTo_NonNullableFlagsEnumType_List(
string flagsEnumValue
)
{
// Arrange
var modelType = typeof(FlagsEnum);
var enumConverter = TypeDescriptor.GetConverter(modelType);
var expected = enumConverter.ConvertFrom(flagsEnumValue).ToString();
var (bindingContext, binder) = GetBinderAndContext(
modelType,
valueProviderValue: flagsEnumValue.Split(","));
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(bindingContext.Result.IsModelSet);
var boundModel = Assert.IsType<FlagsEnum>(bindingContext.Result.Model);
Assert.Equal(expected, boundModel.ToString());
}
[Theory]
[InlineData("8, 1")]
[InlineData("Value2, Value8")]
[InlineData("value8,value4,value2,value1")]
public async Task BindModel_BindsTo_NullableFlagsEnumType_List(
string flagsEnumValue
)
{
// Arrange
var modelType = typeof(FlagsEnum?);
var enumConverter = TypeDescriptor.GetConverter(GetUnderlyingType(modelType));
var expected = enumConverter.ConvertFrom(flagsEnumValue).ToString();
var (bindingContext, binder) = GetBinderAndContext(
modelType,
valueProviderValue: flagsEnumValue.Split(","));
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.True(bindingContext.Result.IsModelSet);
var boundModel = Assert.IsType<FlagsEnum>(bindingContext.Result.Model);
Assert.Equal(expected, boundModel.ToString());
}
[Theory]
[InlineData(typeof(FlagsEnum), "1,16")]
// These two values look like big integers but are treated as two separate enum values that are
// or'd together.
[InlineData(typeof(FlagsEnum), "32,015")]
[InlineData(typeof(FlagsEnum), "32,128")]
[InlineData(typeof(FlagsEnum?), "1,16")]
// These two values look like big integers but are treated as two separate enum values that are
// or'd together.
[InlineData(typeof(FlagsEnum?), "32,015")]
[InlineData(typeof(FlagsEnum?), "32,128")]
public async Task BindModel_AddsErrorToModelState_ForInvalidEnumValues_List(
Type modelType,
string suppliedValue
)
{
// Arrange
var message = $"The value '{suppliedValue}' is invalid.";
var (bindingContext, binder) = GetBinderAndContext(
modelType,
valueProviderValue: suppliedValue.Split(","));
// Act
await binder.BindModelAsync(bindingContext);
// Assert
Assert.False(bindingContext.Result.IsModelSet);
Assert.Null(bindingContext.Result.Model);
Assert.False(bindingContext.ModelState.IsValid);
var error = Assert.Single(bindingContext.ModelState["theModelName"].Errors);
Assert.Equal(message, error.ErrorMessage);
}
private static (DefaultModelBindingContext, IModelBinder) GetBinderAndContext(
Type modelType,
object valueProviderValue)
{
var binderProviderContext = new TestModelBinderProviderContext(modelType);
var modelName = "theModelName";
var bindingContext = new DefaultModelBindingContext
{
ModelMetadata = binderProviderContext.Metadata,
ModelName = modelName,
ModelState = new ModelStateDictionary(),
ValueProvider = new SimpleValueProvider()
{
{ modelName, valueProviderValue }
}
};
var binderProvider = new EnumTypeModelBinderProvider(new MvcOptions());
var binder = binderProvider.GetBinder(binderProviderContext);
return (bindingContext, binder);
}
private static Type GetUnderlyingType(Type modelType)
{
var underlyingType = Nullable.GetUnderlyingType(modelType);
if (underlyingType != null)
{
return underlyingType;
}
return modelType;
}
[Flags]
private enum FlagsEnum
{
Value1 = 1,
Value2 = 2,
Value4 = 4,
Value8 = 8,
}
private enum IntEnum
{
Value0 = 0,
Value1 = 1,
Value2 = 2,
MaxValue = int.MaxValue
}
}
|