Update the Enum Invalid Value setting for compat

Updated the naming to follow guidelines, and set the default for 2.0
apps to false. Note that I inverted the naming, which means that I had
to invert the logic in a few places.
This commit is contained in:
Ryan Nowak 2017-12-28 19:18:12 -08:00
parent 514381f16f
commit 82e32240a4
8 changed files with 67 additions and 39 deletions

View File

@ -48,6 +48,14 @@ namespace Microsoft.AspNetCore.Mvc
/// Sets the default value of settings on <see cref="MvcOptions"/> to match the behavior of /// Sets the default value of settings on <see cref="MvcOptions"/> to match the behavior of
/// ASP.NET Core MVC 2.1. /// ASP.NET Core MVC 2.1.
/// </summary> /// </summary>
/// <remarks>
/// ASP.NET Core MVC 2.1 introduces compatibility switches for the following:
/// <list type="bullet">
/// <item>
/// <description><see cref="MvcOptions.SuppressBindingUndefinedValueToEnumType"/></description>
/// </item>
/// </list>
/// </remarks>
Version_2_1, Version_2_1,
/// <summary> /// <summary>

View File

@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
if (Version >= CompatibilityVersion.Version_2_1) if (Version >= CompatibilityVersion.Version_2_1)
{ {
values[nameof(MvcOptions.SuppressBindingUndefinedValueToEnumType)] = true;
values[nameof(MvcOptions.InputFormatterExceptionModelStatePolicy)] = InputFormatterExceptionModelStatePolicy.MalformedInputExceptions; values[nameof(MvcOptions.InputFormatterExceptionModelStatePolicy)] = InputFormatterExceptionModelStatePolicy.MalformedInputExceptions;
} }

View File

@ -11,9 +11,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// </summary> /// </summary>
public class EnumTypeModelBinder : SimpleTypeModelBinder public class EnumTypeModelBinder : SimpleTypeModelBinder
{ {
private readonly bool _allowBindingUndefinedValueToEnumType; private readonly bool _suppressBindingUndefinedValueToEnumType;
public EnumTypeModelBinder(bool allowBindingUndefinedValueToEnumType, Type modelType) public EnumTypeModelBinder(bool supressBindingUndefinedValueToEnumType, Type modelType)
: base(modelType) : base(modelType)
{ {
if (modelType == null) if (modelType == null)
@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
throw new ArgumentNullException(nameof(modelType)); throw new ArgumentNullException(nameof(modelType));
} }
_allowBindingUndefinedValueToEnumType = allowBindingUndefinedValueToEnumType; _suppressBindingUndefinedValueToEnumType = supressBindingUndefinedValueToEnumType;
} }
protected override void CheckModel( protected override void CheckModel(
@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
ValueProviderResult valueProviderResult, ValueProviderResult valueProviderResult,
object model) object model)
{ {
if (model == null || _allowBindingUndefinedValueToEnumType) if (model == null || !_suppressBindingUndefinedValueToEnumType)
{ {
base.CheckModel(bindingContext, valueProviderResult, model); base.CheckModel(bindingContext, valueProviderResult, model);
} }

View File

@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
if (context.Metadata.IsEnum) if (context.Metadata.IsEnum)
{ {
return new EnumTypeModelBinder( return new EnumTypeModelBinder(
_options.AllowBindingUndefinedValueToEnumType, _options.SuppressBindingUndefinedValueToEnumType,
context.Metadata.UnderlyingOrModelType); context.Metadata.UnderlyingOrModelType);
} }

View File

@ -22,8 +22,8 @@ namespace Microsoft.AspNetCore.Mvc
private int _maxModelStateErrors = ModelStateDictionary.DefaultMaxAllowedErrors; private int _maxModelStateErrors = ModelStateDictionary.DefaultMaxAllowedErrors;
// See CompatibilitySwitch.cs for guide on how to implement these. // See CompatibilitySwitch.cs for guide on how to implement these.
private readonly CompatibilitySwitch<bool> _allowBindingUndefinedValueToEnumType;
private readonly CompatibilitySwitch<InputFormatterExceptionModelStatePolicy> _inputFormatterExceptionModelStatePolicy; private readonly CompatibilitySwitch<InputFormatterExceptionModelStatePolicy> _inputFormatterExceptionModelStatePolicy;
private readonly CompatibilitySwitch<bool> _suppressBindingUndefinedValueToEnumType;
private readonly CompatibilitySwitch<bool> _suppressJsonDeserializationExceptionMessagesInModelState; private readonly CompatibilitySwitch<bool> _suppressJsonDeserializationExceptionMessagesInModelState;
private readonly ICompatibilitySwitch[] _switches; private readonly ICompatibilitySwitch[] _switches;
@ -41,13 +41,13 @@ namespace Microsoft.AspNetCore.Mvc
ModelValidatorProviders = new List<IModelValidatorProvider>(); ModelValidatorProviders = new List<IModelValidatorProvider>();
ValueProviderFactories = new List<IValueProviderFactory>(); ValueProviderFactories = new List<IValueProviderFactory>();
_allowBindingUndefinedValueToEnumType = new CompatibilitySwitch<bool>(nameof(AllowBindingUndefinedValueToEnumType));
_inputFormatterExceptionModelStatePolicy = new CompatibilitySwitch<InputFormatterExceptionModelStatePolicy>(nameof(InputFormatterExceptionModelStatePolicy), InputFormatterExceptionModelStatePolicy.AllExceptions); _inputFormatterExceptionModelStatePolicy = new CompatibilitySwitch<InputFormatterExceptionModelStatePolicy>(nameof(InputFormatterExceptionModelStatePolicy), InputFormatterExceptionModelStatePolicy.AllExceptions);
_suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch<bool>(nameof(SuppressBindingUndefinedValueToEnumType));
_suppressJsonDeserializationExceptionMessagesInModelState = new CompatibilitySwitch<bool>(nameof(SuppressJsonDeserializationExceptionMessagesInModelState)); _suppressJsonDeserializationExceptionMessagesInModelState = new CompatibilitySwitch<bool>(nameof(SuppressJsonDeserializationExceptionMessagesInModelState));
_switches = new ICompatibilitySwitch[] _switches = new ICompatibilitySwitch[]
{ {
_allowBindingUndefinedValueToEnumType,
_inputFormatterExceptionModelStatePolicy, _inputFormatterExceptionModelStatePolicy,
_suppressBindingUndefinedValueToEnumType,
_suppressJsonDeserializationExceptionMessagesInModelState, _suppressJsonDeserializationExceptionMessagesInModelState,
}; };
} }
@ -92,6 +92,35 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary> /// </summary>
public FormatterCollection<IInputFormatter> InputFormatters { get; } public FormatterCollection<IInputFormatter> InputFormatters { get; }
/// <summary>
/// Gets or sets an value indicating whether the model binding system will bind undefined values to
/// enum types. The default value of the property is <c>false</c>.
/// </summary>
/// <remarks>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>
/// Configuring the desired of the value compatibility switch by calling this property's setter will take precedence
/// over the value implied by the application's <see cref="CompatibilityVersion"/>.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_0"/> then
/// this setting will have value <c>false</c> if not explicitly configured.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// higher then this setting will have value <c>true</c> if not explicitly configured.
/// </para>
/// </remarks>
public bool SuppressBindingUndefinedValueToEnumType
{
get => _suppressBindingUndefinedValueToEnumType.Value;
set => _suppressBindingUndefinedValueToEnumType.Value = value;
}
/// <summary> /// <summary>
/// Gets or sets the flag to buffer the request body in input formatters. Default is <c>false</c>. /// Gets or sets the flag to buffer the request body in input formatters. Default is <c>false</c>.
/// </summary> /// </summary>
@ -181,15 +210,6 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary> /// </summary>
public bool RequireHttpsPermanent { get; set; } 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 => _allowBindingUndefinedValueToEnumType.Value;
set => _allowBindingUndefinedValueToEnumType.Value = value;
}
/// <summary> /// <summary>
/// Gets or sets the option to determine if model binding should convert all exceptions (including ones not related to bad input) /// Gets or sets the option to determine if model binding should convert all exceptions (including ones not related to bad input)

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void ReturnsBinder_ForEnumType(Type modelType) public void ReturnsBinder_ForEnumType(Type modelType)
{ {
// Arrange // Arrange
var provider = new EnumTypeModelBinderProvider(new MvcOptions { AllowBindingUndefinedValueToEnumType = true }); var provider = new EnumTypeModelBinderProvider(new MvcOptions());
var context = new TestModelBinderProviderContext(modelType); var context = new TestModelBinderProviderContext(modelType);
// Act // Act
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void ReturnsBinder_ForFlagsEnumType(Type modelType) public void ReturnsBinder_ForFlagsEnumType(Type modelType)
{ {
// Arrange // Arrange
var provider = new EnumTypeModelBinderProvider(new MvcOptions { AllowBindingUndefinedValueToEnumType = true }); var provider = new EnumTypeModelBinderProvider(new MvcOptions());
var context = new TestModelBinderProviderContext(modelType); var context = new TestModelBinderProviderContext(modelType);
// Act // Act
@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void DoesNotReturnBinder_ForNonEnumTypes(Type modelType) public void DoesNotReturnBinder_ForNonEnumTypes(Type modelType)
{ {
// Arrange // Arrange
var provider = new EnumTypeModelBinderProvider(new MvcOptions { AllowBindingUndefinedValueToEnumType = false }); var provider = new EnumTypeModelBinderProvider(new MvcOptions());
var context = new TestModelBinderProviderContext(modelType); var context = new TestModelBinderProviderContext(modelType);
// Act // Act

View File

@ -5,7 +5,6 @@ using System;
using System.ComponentModel; using System.ComponentModel;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.Extensions.Options;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Mvc.ModelBinding namespace Microsoft.AspNetCore.Mvc.ModelBinding
@ -18,13 +17,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
[InlineData(false, typeof(IntEnum?))] [InlineData(false, typeof(IntEnum?))]
[InlineData(false, typeof(FlagsEnum?))] [InlineData(false, typeof(FlagsEnum?))]
public async Task BindModel_SetsModel_ForEmptyValue_AndNullableEnumTypes( public async Task BindModel_SetsModel_ForEmptyValue_AndNullableEnumTypes(
bool allowBindingUndefinedValueToEnumType, bool suppressBindingUndefinedValueToEnumType,
Type modelType) Type modelType)
{ {
// Arrange // Arrange
var binderInfo = GetBinderAndContext( var binderInfo = GetBinderAndContext(
modelType, modelType,
allowBindingUndefinedValueToEnumType, suppressBindingUndefinedValueToEnumType,
valueProviderValue: ""); valueProviderValue: "");
var bindingContext = binderInfo.Item1; var bindingContext = binderInfo.Item1;
var binder = binderInfo.Item2; var binder = binderInfo.Item2;
@ -43,14 +42,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
[InlineData(false, typeof(IntEnum))] [InlineData(false, typeof(IntEnum))]
[InlineData(false, typeof(FlagsEnum))] [InlineData(false, typeof(FlagsEnum))]
public async Task BindModel_AddsErrorToModelState_ForEmptyValue_AndNonNullableEnumTypes( public async Task BindModel_AddsErrorToModelState_ForEmptyValue_AndNonNullableEnumTypes(
bool allowBindingUndefinedValueToEnumType, bool suprressBindingUndefinedValueToEnumType,
Type modelType) Type modelType)
{ {
// Arrange // Arrange
var message = "The value '' is invalid."; var message = "The value '' is invalid.";
var binderInfo = GetBinderAndContext( var binderInfo = GetBinderAndContext(
modelType, modelType,
allowBindingUndefinedValueToEnumType, suprressBindingUndefinedValueToEnumType,
valueProviderValue: ""); valueProviderValue: "");
var bindingContext = binderInfo.Item1; var bindingContext = binderInfo.Item1;
var binder = binderInfo.Item2; var binder = binderInfo.Item2;
@ -72,14 +71,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
[InlineData(false, "Value1")] [InlineData(false, "Value1")]
[InlineData(false, "1")] [InlineData(false, "1")]
public async Task BindModel_BindsEnumModels_ForValuesInArray( public async Task BindModel_BindsEnumModels_ForValuesInArray(
bool allowBindingUndefinedValueToEnumType, bool suppressBindingUndefinedValueToEnumType,
string enumValue) string enumValue)
{ {
// Arrange // Arrange
var modelType = typeof(IntEnum); var modelType = typeof(IntEnum);
var binderInfo = GetBinderAndContext( var binderInfo = GetBinderAndContext(
modelType, modelType,
allowBindingUndefinedValueToEnumType, suppressBindingUndefinedValueToEnumType,
valueProviderValue: new object[] { enumValue }); valueProviderValue: new object[] { enumValue });
var bindingContext = binderInfo.Item1; var bindingContext = binderInfo.Item1;
var binder = binderInfo.Item2; var binder = binderInfo.Item2;
@ -102,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
[InlineData("8, 1", false)] [InlineData("8, 1", false)]
[InlineData("Value2, Value8", false)] [InlineData("Value2, Value8", false)]
[InlineData("value8,value4,value2,value1", false)] [InlineData("value8,value4,value2,value1", false)]
public async Task BindModel_BindsTo_NonNullableFlagsEnumType(string flagsEnumValue, bool allowBindingUndefinedValueToEnumType) public async Task BindModel_BindsTo_NonNullableFlagsEnumType(string flagsEnumValue, bool suppressBindingUndefinedValueToEnumType)
{ {
// Arrange // Arrange
var modelType = typeof(FlagsEnum); var modelType = typeof(FlagsEnum);
@ -110,7 +109,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var expected = enumConverter.ConvertFrom(flagsEnumValue).ToString(); var expected = enumConverter.ConvertFrom(flagsEnumValue).ToString();
var binderInfo = GetBinderAndContext( var binderInfo = GetBinderAndContext(
modelType, modelType,
allowBindingUndefinedValueToEnumType, suppressBindingUndefinedValueToEnumType,
valueProviderValue: new object[] { flagsEnumValue }); valueProviderValue: new object[] { flagsEnumValue });
var bindingContext = binderInfo.Item1; var bindingContext = binderInfo.Item1;
var binder = binderInfo.Item2; var binder = binderInfo.Item2;
@ -133,7 +132,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
[InlineData("8, 1", false)] [InlineData("8, 1", false)]
[InlineData("Value2, Value8", false)] [InlineData("Value2, Value8", false)]
[InlineData("value8,value4,value2,value1", false)] [InlineData("value8,value4,value2,value1", false)]
public async Task BindModel_BindsTo_NullableFlagsEnumType(string flagsEnumValue, bool allowBindingUndefinedValueToEnumType) public async Task BindModel_BindsTo_NullableFlagsEnumType(string flagsEnumValue, bool suppressBindingUndefinedValueToEnumType)
{ {
// Arrange // Arrange
var modelType = typeof(FlagsEnum?); var modelType = typeof(FlagsEnum?);
@ -141,7 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var expected = enumConverter.ConvertFrom(flagsEnumValue).ToString(); var expected = enumConverter.ConvertFrom(flagsEnumValue).ToString();
var binderInfo = GetBinderAndContext( var binderInfo = GetBinderAndContext(
modelType, modelType,
allowBindingUndefinedValueToEnumType, suppressBindingUndefinedValueToEnumType,
valueProviderValue: new object[] { flagsEnumValue }); valueProviderValue: new object[] { flagsEnumValue });
var bindingContext = binderInfo.Item1; var bindingContext = binderInfo.Item1;
var binder = binderInfo.Item2; var binder = binderInfo.Item2;
@ -168,7 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var message = $"The value '{suppliedValue}' is not valid."; var message = $"The value '{suppliedValue}' is not valid.";
var binderInfo = GetBinderAndContext( var binderInfo = GetBinderAndContext(
modelType, modelType,
allowBindingUndefinedValueToEnumType: true, suppressBindingUndefinedValueToEnumType: false,
valueProviderValue: new object[] { suppliedValue }); valueProviderValue: new object[] { suppliedValue });
var bindingContext = binderInfo.Item1; var bindingContext = binderInfo.Item1;
var binder = binderInfo.Item2; var binder = binderInfo.Item2;
@ -209,7 +208,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var message = $"The value '{suppliedValue}' is invalid."; var message = $"The value '{suppliedValue}' is invalid.";
var binderInfo = GetBinderAndContext( var binderInfo = GetBinderAndContext(
modelType, modelType,
allowBindingUndefinedValueToEnumType: false, suppressBindingUndefinedValueToEnumType: true,
valueProviderValue: new object[] { suppliedValue }); valueProviderValue: new object[] { suppliedValue });
var bindingContext = binderInfo.Item1; var bindingContext = binderInfo.Item1;
var binder = binderInfo.Item2; var binder = binderInfo.Item2;
@ -251,7 +250,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var binderProviderContext = new TestModelBinderProviderContext(modelType); var binderProviderContext = new TestModelBinderProviderContext(modelType);
var binderInfo = GetBinderAndContext( var binderInfo = GetBinderAndContext(
modelType, modelType,
allowBindingUndefinedValueToEnumType: true, suppressBindingUndefinedValueToEnumType: false,
valueProviderValue: new object[] { suppliedValue }); valueProviderValue: new object[] { suppliedValue });
var bindingContext = binderInfo.Item1; var bindingContext = binderInfo.Item1;
var binder = binderInfo.Item2; var binder = binderInfo.Item2;
@ -267,7 +266,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
private static (DefaultModelBindingContext, IModelBinder) GetBinderAndContext( private static (DefaultModelBindingContext, IModelBinder) GetBinderAndContext(
Type modelType, Type modelType,
bool allowBindingUndefinedValueToEnumType, bool suppressBindingUndefinedValueToEnumType,
object valueProviderValue) object valueProviderValue)
{ {
var binderProviderContext = new TestModelBinderProviderContext(modelType); var binderProviderContext = new TestModelBinderProviderContext(modelType);
@ -284,7 +283,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
}; };
var binderProvider = new EnumTypeModelBinderProvider(new MvcOptions var binderProvider = new EnumTypeModelBinderProvider(new MvcOptions
{ {
AllowBindingUndefinedValueToEnumType = allowBindingUndefinedValueToEnumType SuppressBindingUndefinedValueToEnumType = suppressBindingUndefinedValueToEnumType
}); });
var binder = binderProvider.GetBinder(binderProviderContext); var binder = binderProvider.GetBinder(binderProviderContext);
return (bindingContext, binder); return (bindingContext, binder);

View File

@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value; var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
// Assert // Assert
Assert.False(mvcOptions.AllowBindingUndefinedValueToEnumType); Assert.False(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionModelStatePolicy.AllExceptions, mvcOptions.InputFormatterExceptionModelStatePolicy); Assert.Equal(InputFormatterExceptionModelStatePolicy.AllExceptions, mvcOptions.InputFormatterExceptionModelStatePolicy);
Assert.False(mvcOptions.SuppressJsonDeserializationExceptionMessagesInModelState); // This name needs to be inverted in #7157 Assert.False(mvcOptions.SuppressJsonDeserializationExceptionMessagesInModelState); // This name needs to be inverted in #7157
} }
@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value; var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
// Assert // Assert
Assert.True(mvcOptions.AllowBindingUndefinedValueToEnumType); Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionModelStatePolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionModelStatePolicy); Assert.Equal(InputFormatterExceptionModelStatePolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionModelStatePolicy);
Assert.True(mvcOptions.SuppressJsonDeserializationExceptionMessagesInModelState); // This name needs to be inverted in #7157 Assert.True(mvcOptions.SuppressJsonDeserializationExceptionMessagesInModelState); // This name needs to be inverted in #7157
} }
@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value; var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
// Assert // Assert
Assert.True(mvcOptions.AllowBindingUndefinedValueToEnumType); Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionModelStatePolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionModelStatePolicy); Assert.Equal(InputFormatterExceptionModelStatePolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionModelStatePolicy);
Assert.True(mvcOptions.SuppressJsonDeserializationExceptionMessagesInModelState); // This name needs to be inverted in #7157 Assert.True(mvcOptions.SuppressJsonDeserializationExceptionMessagesInModelState); // This name needs to be inverted in #7157
} }