Added a model binder for handling Enum types. [Fixes #6329] Why don't we check for Enum.IsDefined on action parameters of enum type
This commit is contained in:
parent
db2d9ee56f
commit
35601f95b3
|
|
@ -46,6 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
options.ModelBinderProviders.Add(new BodyModelBinderProvider(options.InputFormatters, _readerFactory, _loggerFactory, options));
|
||||
options.ModelBinderProviders.Add(new HeaderModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new FloatingPointTypeModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new EnumTypeModelBinderProvider(options.AllowBindingUndefinedValueToEnumType));
|
||||
options.ModelBinderProviders.Add(new SimpleTypeModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new CancellationTokenModelBinderProvider());
|
||||
options.ModelBinderProviders.Add(new ByteArrayModelBinderProvider());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
// 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.Globalization;
|
||||
|
||||
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
|
||||
{
|
||||
private readonly bool _allowBindingUndefinedValueToEnumType;
|
||||
|
||||
public EnumTypeModelBinder(bool allowBindingUndefinedValueToEnumType, Type modelType)
|
||||
: base(modelType)
|
||||
{
|
||||
if (modelType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelType));
|
||||
}
|
||||
|
||||
_allowBindingUndefinedValueToEnumType = allowBindingUndefinedValueToEnumType;
|
||||
}
|
||||
|
||||
protected override void CheckModel(
|
||||
ModelBindingContext bindingContext,
|
||||
ValueProviderResult valueProviderResult,
|
||||
object model)
|
||||
{
|
||||
if (model == null || _allowBindingUndefinedValueToEnumType)
|
||||
{
|
||||
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 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.
|
||||
// Exmaples:
|
||||
//
|
||||
// [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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IModelBinderProvider"/> for types deriving from <see cref="Enum"/>.
|
||||
/// </summary>
|
||||
public class EnumTypeModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
private readonly bool _allowBindingUndefinedValueToEnumType;
|
||||
|
||||
public EnumTypeModelBinderProvider(bool allowBindingUndefinedValueToEnumType)
|
||||
{
|
||||
_allowBindingUndefinedValueToEnumType = allowBindingUndefinedValueToEnumType;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.Metadata.IsEnum)
|
||||
{
|
||||
return new EnumTypeModelBinder(
|
||||
_allowBindingUndefinedValueToEnumType,
|
||||
context.Metadata.UnderlyingOrModelType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -72,23 +72,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
value: value);
|
||||
}
|
||||
|
||||
// When converting newModel a null value may indicate a failed conversion for an otherwise required
|
||||
// model (can't set a ValueType to null). This detects if a null model value is acceptable given the
|
||||
// current bindingContext. If not, an error is logged.
|
||||
if (model == null && !bindingContext.ModelMetadata.IsReferenceOrNullableType)
|
||||
{
|
||||
bindingContext.ModelState.TryAddModelError(
|
||||
bindingContext.ModelName,
|
||||
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
|
||||
valueProviderResult.ToString()));
|
||||
CheckModel(bindingContext, valueProviderResult, model);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Success(model);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
|
|
@ -109,5 +95,26 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void CheckModel(
|
||||
ModelBindingContext bindingContext,
|
||||
ValueProviderResult valueProviderResult,
|
||||
object model)
|
||||
{
|
||||
// When converting newModel a null value may indicate a failed conversion for an otherwise required
|
||||
// model (can't set a ValueType to null). This detects if a null model value is acceptable given the
|
||||
// current bindingContext. If not, an error is logged.
|
||||
if (model == null && !bindingContext.ModelMetadata.IsReferenceOrNullableType)
|
||||
{
|
||||
bindingContext.ModelState.TryAddModelError(
|
||||
bindingContext.ModelName,
|
||||
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
|
||||
valueProviderResult.ToString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Success(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,5 +162,11 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// Gets or sets the default value for the Permanent property of <see cref="RequireHttpsAttribute"/>.
|
||||
/// </summary>
|
||||
public bool RequireHttpsPermanent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an indication whether the model binding system will bind undefined values to enumeration types.
|
||||
/// <see langword="false"/> by default.
|
||||
/// </summary>
|
||||
public bool AllowBindingUndefinedValueToEnumType { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
public class EnumTypeModelBinderProviderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(typeof(CarType))]
|
||||
[InlineData(typeof(CarType?))]
|
||||
public void ReturnsBinder_ForEnumType(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new EnumTypeModelBinderProvider(allowBindingUndefinedValueToEnumType: true);
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<EnumTypeModelBinder>(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(CarOptions))]
|
||||
[InlineData(typeof(CarOptions?))]
|
||||
public void ReturnsBinder_ForFlagsEnumType(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new EnumTypeModelBinderProvider(allowBindingUndefinedValueToEnumType: true);
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<EnumTypeModelBinder>(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(string))]
|
||||
[InlineData(typeof(int))]
|
||||
[InlineData(typeof(int?))]
|
||||
public void DoesNotReturnBinder_ForNonEnumTypes(Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new EnumTypeModelBinderProvider(allowBindingUndefinedValueToEnumType: false);
|
||||
var context = new TestModelBinderProviderContext(modelType);
|
||||
|
||||
// Act
|
||||
var result = provider.GetBinder(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
enum CarType
|
||||
{
|
||||
Sedan,
|
||||
Coupe
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum CarOptions
|
||||
{
|
||||
SunRoof = 0x01,
|
||||
Spoiler = 0x02,
|
||||
FogLights = 0x04,
|
||||
TintedWindows = 0x08,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
// 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.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
public class EnumTypeModelBinderTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(true, typeof(IntEnum?))]
|
||||
[InlineData(true, typeof(FlagsEnum?))]
|
||||
[InlineData(false, typeof(IntEnum?))]
|
||||
[InlineData(false, typeof(FlagsEnum?))]
|
||||
public async Task BindModel_SetsModel_ForEmptyValue_AndNullableEnumTypes(
|
||||
bool allowBindingUndefinedValueToEnumType,
|
||||
Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var binderInfo = GetBinderAndContext(
|
||||
modelType,
|
||||
allowBindingUndefinedValueToEnumType,
|
||||
valueProviderValue: "");
|
||||
var bindingContext = binderInfo.Item1;
|
||||
var binder = binderInfo.Item2;
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(bindingContext.Result.IsModelSet);
|
||||
Assert.Null(bindingContext.Result.Model);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true, typeof(IntEnum))]
|
||||
[InlineData(true, typeof(FlagsEnum))]
|
||||
[InlineData(false, typeof(IntEnum))]
|
||||
[InlineData(false, typeof(FlagsEnum))]
|
||||
public async Task BindModel_AddsErrorToModelState_ForEmptyValue_AndNonNullableEnumTypes(
|
||||
bool allowBindingUndefinedValueToEnumType,
|
||||
Type modelType)
|
||||
{
|
||||
// Arrange
|
||||
var message = "The value '' is invalid.";
|
||||
var binderInfo = GetBinderAndContext(
|
||||
modelType,
|
||||
allowBindingUndefinedValueToEnumType,
|
||||
valueProviderValue: "");
|
||||
var bindingContext = binderInfo.Item1;
|
||||
var binder = binderInfo.Item2;
|
||||
|
||||
// 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(true, "Value1")]
|
||||
[InlineData(true, "1")]
|
||||
[InlineData(false, "Value1")]
|
||||
[InlineData(false, "1")]
|
||||
public async Task BindModel_BindsEnumModels_ForValuesInArray(
|
||||
bool allowBindingUndefinedValueToEnumType,
|
||||
string enumValue)
|
||||
{
|
||||
// Arrange
|
||||
var modelType = typeof(IntEnum);
|
||||
var binderInfo = GetBinderAndContext(
|
||||
modelType,
|
||||
allowBindingUndefinedValueToEnumType,
|
||||
valueProviderValue: new object[] { enumValue });
|
||||
var bindingContext = binderInfo.Item1;
|
||||
var binder = binderInfo.Item2;
|
||||
|
||||
// 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", true)]
|
||||
[InlineData("8, 1", true)]
|
||||
[InlineData("Value2, Value8", true)]
|
||||
[InlineData("value8,value4,value2,value1", true)]
|
||||
[InlineData("1", false)]
|
||||
[InlineData("8, 1", false)]
|
||||
[InlineData("Value2, Value8", false)]
|
||||
[InlineData("value8,value4,value2,value1", false)]
|
||||
public async Task BindModel_BindsTo_NonNullableFlagsEnumType(string flagsEnumValue, bool allowBindingUndefinedValueToEnumType)
|
||||
{
|
||||
// Arrange
|
||||
var modelType = typeof(FlagsEnum);
|
||||
var enumConverter = TypeDescriptor.GetConverter(modelType);
|
||||
var expected = enumConverter.ConvertFrom(flagsEnumValue).ToString();
|
||||
var binderInfo = GetBinderAndContext(
|
||||
modelType,
|
||||
allowBindingUndefinedValueToEnumType,
|
||||
valueProviderValue: new object[] { flagsEnumValue });
|
||||
var bindingContext = binderInfo.Item1;
|
||||
var binder = binderInfo.Item2;
|
||||
|
||||
// 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", true)]
|
||||
[InlineData("8, 1", true)]
|
||||
[InlineData("Value2, Value8", true)]
|
||||
[InlineData("value8,value4,value2,value1", true)]
|
||||
[InlineData("1", false)]
|
||||
[InlineData("8, 1", false)]
|
||||
[InlineData("Value2, Value8", false)]
|
||||
[InlineData("value8,value4,value2,value1", false)]
|
||||
public async Task BindModel_BindsTo_NullableFlagsEnumType(string flagsEnumValue, bool allowBindingUndefinedValueToEnumType)
|
||||
{
|
||||
// Arrange
|
||||
var modelType = typeof(FlagsEnum?);
|
||||
var enumConverter = TypeDescriptor.GetConverter(GetUnderlyingType(modelType));
|
||||
var expected = enumConverter.ConvertFrom(flagsEnumValue).ToString();
|
||||
var binderInfo = GetBinderAndContext(
|
||||
modelType,
|
||||
allowBindingUndefinedValueToEnumType,
|
||||
valueProviderValue: new object[] { flagsEnumValue });
|
||||
var bindingContext = binderInfo.Item1;
|
||||
var binder = binderInfo.Item2;
|
||||
|
||||
// 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), "Value10")]
|
||||
[InlineData(typeof(FlagsEnum), "Value1, Value10")]
|
||||
[InlineData(typeof(FlagsEnum), "value10, value1")]
|
||||
[InlineData(typeof(FlagsEnum?), "Value10")]
|
||||
[InlineData(typeof(FlagsEnum?), "Value1, Value10")]
|
||||
[InlineData(typeof(FlagsEnum?), "value10, value1")]
|
||||
public async Task BindModel_AddsErrorToModelState_ForInvalidEnumValues_IsNotValidMessage(Type modelType, string suppliedValue)
|
||||
{
|
||||
// Arrange
|
||||
var message = $"The value '{suppliedValue}' is not valid.";
|
||||
var binderInfo = GetBinderAndContext(
|
||||
modelType,
|
||||
allowBindingUndefinedValueToEnumType: true,
|
||||
valueProviderValue: new object[] { suppliedValue });
|
||||
var bindingContext = binderInfo.Item1;
|
||||
var binder = binderInfo.Item2;
|
||||
|
||||
|
||||
// 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(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 binderInfo = GetBinderAndContext(
|
||||
modelType,
|
||||
allowBindingUndefinedValueToEnumType: false,
|
||||
valueProviderValue: new object[] { suppliedValue });
|
||||
var bindingContext = binderInfo.Item1;
|
||||
var binder = binderInfo.Item2;
|
||||
|
||||
// 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(typeof(IntEnum), "3", 3)]
|
||||
[InlineData(typeof(FlagsEnum), "19", 19)]
|
||||
[InlineData(typeof(FlagsEnum), "0", 0)]
|
||||
[InlineData(typeof(FlagsEnum), "1, 16", 17)]
|
||||
// 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", 47)]
|
||||
[InlineData(typeof(FlagsEnum), "32,128", 160)]
|
||||
[InlineData(typeof(IntEnum?), "3", 3)]
|
||||
[InlineData(typeof(FlagsEnum?), "19", 19)]
|
||||
[InlineData(typeof(FlagsEnum?), "0", 0)]
|
||||
[InlineData(typeof(FlagsEnum?), "1, 16", 17)]
|
||||
// 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", 47)]
|
||||
[InlineData(typeof(FlagsEnum?), "32,128", 160)]
|
||||
public async Task BindModel_AllowsBindingUndefinedValues_ToEnumTypes(
|
||||
Type modelType,
|
||||
string suppliedValue,
|
||||
long expected)
|
||||
{
|
||||
// Arrange
|
||||
var binderProviderContext = new TestModelBinderProviderContext(modelType);
|
||||
var binderInfo = GetBinderAndContext(
|
||||
modelType,
|
||||
allowBindingUndefinedValueToEnumType: true,
|
||||
valueProviderValue: new object[] { suppliedValue });
|
||||
var bindingContext = binderInfo.Item1;
|
||||
var binder = binderInfo.Item2;
|
||||
|
||||
// Act
|
||||
await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(bindingContext.Result.IsModelSet);
|
||||
Assert.IsType(GetUnderlyingType(modelType), bindingContext.Result.Model);
|
||||
Assert.Equal(expected, Convert.ToInt64(bindingContext.Result.Model));
|
||||
}
|
||||
|
||||
private static (DefaultModelBindingContext, IModelBinder) GetBinderAndContext(
|
||||
Type modelType,
|
||||
bool allowBindingUndefinedValueToEnumType,
|
||||
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(allowBindingUndefinedValueToEnumType);
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
binder => Assert.IsType<BodyModelBinderProvider>(binder),
|
||||
binder => Assert.IsType<HeaderModelBinderProvider>(binder),
|
||||
binder => Assert.IsType<FloatingPointTypeModelBinderProvider>(binder),
|
||||
binder => Assert.IsType<EnumTypeModelBinderProvider>(binder),
|
||||
binder => Assert.IsType<SimpleTypeModelBinderProvider>(binder),
|
||||
binder => Assert.IsType<CancellationTokenModelBinderProvider>(binder),
|
||||
binder => Assert.IsType<ByteArrayModelBinderProvider>(binder),
|
||||
|
|
|
|||
Loading…
Reference in New Issue