diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs b/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs index c49c27f454..aee3edea86 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs @@ -48,6 +48,14 @@ namespace Microsoft.AspNetCore.Mvc /// Sets the default value of settings on to match the behavior of /// ASP.NET Core MVC 2.1. /// + /// + /// ASP.NET Core MVC 2.1 introduces compatibility switches for the following: + /// + /// + /// + /// + /// + /// Version_2_1, /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs index e722e91935..0121ac86d0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs @@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure if (Version >= CompatibilityVersion.Version_2_1) { + values[nameof(MvcOptions.SuppressBindingUndefinedValueToEnumType)] = true; values[nameof(MvcOptions.InputFormatterExceptionModelStatePolicy)] = InputFormatterExceptionModelStatePolicy.MalformedInputExceptions; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs index 4787e0528e..6348b5f11c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs @@ -11,9 +11,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders /// 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) { if (modelType == null) @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders throw new ArgumentNullException(nameof(modelType)); } - _allowBindingUndefinedValueToEnumType = allowBindingUndefinedValueToEnumType; + _suppressBindingUndefinedValueToEnumType = supressBindingUndefinedValueToEnumType; } protected override void CheckModel( @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders ValueProviderResult valueProviderResult, object model) { - if (model == null || _allowBindingUndefinedValueToEnumType) + if (model == null || !_suppressBindingUndefinedValueToEnumType) { base.CheckModel(bindingContext, valueProviderResult, model); } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs index 5bf700e536..140913a1bb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders if (context.Metadata.IsEnum) { return new EnumTypeModelBinder( - _options.AllowBindingUndefinedValueToEnumType, + _options.SuppressBindingUndefinedValueToEnumType, context.Metadata.UnderlyingOrModelType); } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs index b6dab32f73..6ae6d5edcf 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs @@ -22,8 +22,8 @@ namespace Microsoft.AspNetCore.Mvc private int _maxModelStateErrors = ModelStateDictionary.DefaultMaxAllowedErrors; // See CompatibilitySwitch.cs for guide on how to implement these. - private readonly CompatibilitySwitch _allowBindingUndefinedValueToEnumType; private readonly CompatibilitySwitch _inputFormatterExceptionModelStatePolicy; + private readonly CompatibilitySwitch _suppressBindingUndefinedValueToEnumType; private readonly CompatibilitySwitch _suppressJsonDeserializationExceptionMessagesInModelState; private readonly ICompatibilitySwitch[] _switches; @@ -41,13 +41,13 @@ namespace Microsoft.AspNetCore.Mvc ModelValidatorProviders = new List(); ValueProviderFactories = new List(); - _allowBindingUndefinedValueToEnumType = new CompatibilitySwitch(nameof(AllowBindingUndefinedValueToEnumType)); _inputFormatterExceptionModelStatePolicy = new CompatibilitySwitch(nameof(InputFormatterExceptionModelStatePolicy), InputFormatterExceptionModelStatePolicy.AllExceptions); + _suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch(nameof(SuppressBindingUndefinedValueToEnumType)); _suppressJsonDeserializationExceptionMessagesInModelState = new CompatibilitySwitch(nameof(SuppressJsonDeserializationExceptionMessagesInModelState)); _switches = new ICompatibilitySwitch[] { - _allowBindingUndefinedValueToEnumType, _inputFormatterExceptionModelStatePolicy, + _suppressBindingUndefinedValueToEnumType, _suppressJsonDeserializationExceptionMessagesInModelState, }; } @@ -92,6 +92,35 @@ namespace Microsoft.AspNetCore.Mvc /// public FormatterCollection InputFormatters { get; } + /// + /// 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 false. + /// + /// + /// + /// This property is associated with a compatibility switch and can provide a different behavior depending on + /// the configured compatibility version for the application. See for + /// guidance and examples of setting the application's compatibility version. + /// + /// + /// 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 . + /// + /// + /// If the application's compatibility version is set to then + /// this setting will have value false if not explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// higher then this setting will have value true if not explicitly configured. + /// + /// + public bool SuppressBindingUndefinedValueToEnumType + { + get => _suppressBindingUndefinedValueToEnumType.Value; + set => _suppressBindingUndefinedValueToEnumType.Value = value; + } + /// /// Gets or sets the flag to buffer the request body in input formatters. Default is false. /// @@ -181,15 +210,6 @@ namespace Microsoft.AspNetCore.Mvc /// public bool RequireHttpsPermanent { get; set; } - /// - /// Gets or sets an indication whether the model binding system will bind undefined values to enumeration types. - /// by default. - /// - public bool AllowBindingUndefinedValueToEnumType - { - get => _allowBindingUndefinedValueToEnumType.Value; - set => _allowBindingUndefinedValueToEnumType.Value = value; - } /// /// Gets or sets the option to determine if model binding should convert all exceptions (including ones not related to bad input) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderProviderTest.cs index 70444d731c..017f9a4e48 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderProviderTest.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public void ReturnsBinder_ForEnumType(Type modelType) { // Arrange - var provider = new EnumTypeModelBinderProvider(new MvcOptions { AllowBindingUndefinedValueToEnumType = true }); + var provider = new EnumTypeModelBinderProvider(new MvcOptions()); var context = new TestModelBinderProviderContext(modelType); // Act @@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public void ReturnsBinder_ForFlagsEnumType(Type modelType) { // Arrange - var provider = new EnumTypeModelBinderProvider(new MvcOptions { AllowBindingUndefinedValueToEnumType = true }); + var provider = new EnumTypeModelBinderProvider(new MvcOptions()); var context = new TestModelBinderProviderContext(modelType); // Act @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public void DoesNotReturnBinder_ForNonEnumTypes(Type modelType) { // Arrange - var provider = new EnumTypeModelBinderProvider(new MvcOptions { AllowBindingUndefinedValueToEnumType = false }); + var provider = new EnumTypeModelBinderProvider(new MvcOptions()); var context = new TestModelBinderProviderContext(modelType); // Act diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs index 2379c0c933..e7c4a2bf2b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs @@ -5,7 +5,6 @@ using System; using System.ComponentModel; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; -using Microsoft.Extensions.Options; using Xunit; namespace Microsoft.AspNetCore.Mvc.ModelBinding @@ -18,13 +17,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding [InlineData(false, typeof(IntEnum?))] [InlineData(false, typeof(FlagsEnum?))] public async Task BindModel_SetsModel_ForEmptyValue_AndNullableEnumTypes( - bool allowBindingUndefinedValueToEnumType, + bool suppressBindingUndefinedValueToEnumType, Type modelType) { // Arrange var binderInfo = GetBinderAndContext( modelType, - allowBindingUndefinedValueToEnumType, + suppressBindingUndefinedValueToEnumType, valueProviderValue: ""); var bindingContext = binderInfo.Item1; var binder = binderInfo.Item2; @@ -43,14 +42,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding [InlineData(false, typeof(IntEnum))] [InlineData(false, typeof(FlagsEnum))] public async Task BindModel_AddsErrorToModelState_ForEmptyValue_AndNonNullableEnumTypes( - bool allowBindingUndefinedValueToEnumType, + bool suprressBindingUndefinedValueToEnumType, Type modelType) { // Arrange var message = "The value '' is invalid."; var binderInfo = GetBinderAndContext( modelType, - allowBindingUndefinedValueToEnumType, + suprressBindingUndefinedValueToEnumType, valueProviderValue: ""); var bindingContext = binderInfo.Item1; var binder = binderInfo.Item2; @@ -72,14 +71,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding [InlineData(false, "Value1")] [InlineData(false, "1")] public async Task BindModel_BindsEnumModels_ForValuesInArray( - bool allowBindingUndefinedValueToEnumType, + bool suppressBindingUndefinedValueToEnumType, string enumValue) { // Arrange var modelType = typeof(IntEnum); var binderInfo = GetBinderAndContext( modelType, - allowBindingUndefinedValueToEnumType, + suppressBindingUndefinedValueToEnumType, valueProviderValue: new object[] { enumValue }); var bindingContext = binderInfo.Item1; var binder = binderInfo.Item2; @@ -102,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding [InlineData("8, 1", false)] [InlineData("Value2, Value8", 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 var modelType = typeof(FlagsEnum); @@ -110,7 +109,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding var expected = enumConverter.ConvertFrom(flagsEnumValue).ToString(); var binderInfo = GetBinderAndContext( modelType, - allowBindingUndefinedValueToEnumType, + suppressBindingUndefinedValueToEnumType, valueProviderValue: new object[] { flagsEnumValue }); var bindingContext = binderInfo.Item1; var binder = binderInfo.Item2; @@ -133,7 +132,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding [InlineData("8, 1", false)] [InlineData("Value2, Value8", 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 var modelType = typeof(FlagsEnum?); @@ -141,7 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding var expected = enumConverter.ConvertFrom(flagsEnumValue).ToString(); var binderInfo = GetBinderAndContext( modelType, - allowBindingUndefinedValueToEnumType, + suppressBindingUndefinedValueToEnumType, valueProviderValue: new object[] { flagsEnumValue }); var bindingContext = binderInfo.Item1; var binder = binderInfo.Item2; @@ -168,7 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding var message = $"The value '{suppliedValue}' is not valid."; var binderInfo = GetBinderAndContext( modelType, - allowBindingUndefinedValueToEnumType: true, + suppressBindingUndefinedValueToEnumType: false, valueProviderValue: new object[] { suppliedValue }); var bindingContext = binderInfo.Item1; var binder = binderInfo.Item2; @@ -209,7 +208,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding var message = $"The value '{suppliedValue}' is invalid."; var binderInfo = GetBinderAndContext( modelType, - allowBindingUndefinedValueToEnumType: false, + suppressBindingUndefinedValueToEnumType: true, valueProviderValue: new object[] { suppliedValue }); var bindingContext = binderInfo.Item1; var binder = binderInfo.Item2; @@ -251,7 +250,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding var binderProviderContext = new TestModelBinderProviderContext(modelType); var binderInfo = GetBinderAndContext( modelType, - allowBindingUndefinedValueToEnumType: true, + suppressBindingUndefinedValueToEnumType: false, valueProviderValue: new object[] { suppliedValue }); var bindingContext = binderInfo.Item1; var binder = binderInfo.Item2; @@ -267,7 +266,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding private static (DefaultModelBindingContext, IModelBinder) GetBinderAndContext( Type modelType, - bool allowBindingUndefinedValueToEnumType, + bool suppressBindingUndefinedValueToEnumType, object valueProviderValue) { var binderProviderContext = new TestModelBinderProviderContext(modelType); @@ -284,7 +283,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding }; var binderProvider = new EnumTypeModelBinderProvider(new MvcOptions { - AllowBindingUndefinedValueToEnumType = allowBindingUndefinedValueToEnumType + SuppressBindingUndefinedValueToEnumType = suppressBindingUndefinedValueToEnumType }); var binder = binderProvider.GetBinder(binderProviderContext); return (bindingContext, binder); diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs index 102fd5a316..45dabaf9df 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var mvcOptions = services.GetRequiredService>().Value; // Assert - Assert.False(mvcOptions.AllowBindingUndefinedValueToEnumType); + Assert.False(mvcOptions.SuppressBindingUndefinedValueToEnumType); Assert.Equal(InputFormatterExceptionModelStatePolicy.AllExceptions, mvcOptions.InputFormatterExceptionModelStatePolicy); 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>().Value; // Assert - Assert.True(mvcOptions.AllowBindingUndefinedValueToEnumType); + Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType); Assert.Equal(InputFormatterExceptionModelStatePolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionModelStatePolicy); 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>().Value; // Assert - Assert.True(mvcOptions.AllowBindingUndefinedValueToEnumType); + Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType); Assert.Equal(InputFormatterExceptionModelStatePolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionModelStatePolicy); Assert.True(mvcOptions.SuppressJsonDeserializationExceptionMessagesInModelState); // This name needs to be inverted in #7157 }