diff options
author | Steve Dunn <steve@dunnhq.com> | 2022-11-07 02:08:52 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-07 02:08:52 +0300 |
commit | 264d7391ec9f6e698051db0621c5e090d0ae4710 (patch) | |
tree | 282613e42228b17414a2fa8d8558b6fb122d5176 | |
parent | fb001c47d8fe6575c390fa7fc47b039917882841 (diff) |
Control if invalid configuration values are swallowed or thrown (#77708)
4 files changed, 146 insertions, 15 deletions
diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/BinderOptions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/BinderOptions.cs index f5ffd6eb575..b4664675b15 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/BinderOptions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/BinderOptions.cs @@ -15,10 +15,10 @@ namespace Microsoft.Extensions.Configuration public bool BindNonPublicProperties { get; set; } /// <summary> - /// When false (the default), no exceptions are thrown when a configuration key is found for which the - /// provided model object does not have an appropriate property which matches the key's name. + /// When false (the default), no exceptions are thrown when trying to convert a value or when a configuration + /// key is found for which the provided model object does not have an appropriate property which matches the key's name. /// When true, an <see cref="System.InvalidOperationException"/> is thrown with a description - /// of the missing properties. + /// of the error. /// </summary> public bool ErrorOnUnknownConfiguration { get; set; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index 1d0992781f0..332f74ef794 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -620,8 +620,13 @@ namespace Microsoft.Extensions.Configuration setter.SetValue(dictionary, valueBindingPoint.Value, new object[] { key }); } } - catch + catch(Exception ex) { + if (options.ErrorOnUnknownConfiguration) + { + throw new InvalidOperationException(SR.Format(SR.Error_GeneralErrorWhenBinding, + nameof(options.ErrorOnUnknownConfiguration)), ex); + } } } } @@ -653,8 +658,14 @@ namespace Microsoft.Extensions.Configuration addMethod?.Invoke(collection, new[] { itemBindingPoint.Value }); } } - catch + catch(Exception ex) { + if (options.ErrorOnUnknownConfiguration) + { + throw new InvalidOperationException(SR.Format(SR.Error_GeneralErrorWhenBinding, + nameof(options.ErrorOnUnknownConfiguration)), ex); + } + } } } @@ -702,8 +713,13 @@ namespace Microsoft.Extensions.Configuration list.Add(itemBindingPoint.Value); } } - catch + catch (Exception ex) { + if (options.ErrorOnUnknownConfiguration) + { + throw new InvalidOperationException(SR.Format(SR.Error_GeneralErrorWhenBinding, + nameof(options.ErrorOnUnknownConfiguration)), ex); + } } } @@ -761,8 +777,13 @@ namespace Microsoft.Extensions.Configuration addMethod.Invoke(instance, arguments); } } - catch + catch (Exception ex) { + if (options.ErrorOnUnknownConfiguration) + { + throw new InvalidOperationException(SR.Format(SR.Error_GeneralErrorWhenBinding, + nameof(options.ErrorOnUnknownConfiguration)), ex); + } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Resources/Strings.resx index 197b325252e..a926fc42386 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Resources/Strings.resx +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Resources/Strings.resx @@ -132,6 +132,9 @@ <data name="Error_FailedToActivate" xml:space="preserve"> <value>Failed to create instance of type '{0}'.</value> </data> + <data name="Error_GeneralErrorWhenBinding" xml:space="preserve"> + <value>'{0}' was set and binding has failed. The likely cause is an invalid configuration value.</value> + </data> <data name="Error_MissingConfig" xml:space="preserve"> <value>'{0}' was set on the provided {1}, but the following properties were not found on the instance of {2}: {3}</value> </data> diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs index a24e7d3b9a3..51a25429f77 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs @@ -75,20 +75,20 @@ namespace Microsoft.Extensions.Configuration.Binder.Test public ISet<string> ISetNoSetter { get; } = new HashSet<string>(); public HashSet<string> InstantiatedHashSetWithSomeValues { get; set; } = - new HashSet<string>(new[] {"existing1", "existing2"}); + new HashSet<string>(new[] { "existing1", "existing2" }); public SortedSet<string> InstantiatedSortedSetWithSomeValues { get; set; } = - new SortedSet<string>(new[] {"existing1", "existing2"}); + new SortedSet<string>(new[] { "existing1", "existing2" }); public SortedSet<string> NonInstantiatedSortedSetWithSomeValues { get; set; } = null!; public ISet<string> InstantiatedISetWithSomeValues { get; set; } = new HashSet<string>(new[] { "existing1", "existing2" }); - + public ISet<UnsupportedTypeInHashSet> HashSetWithUnsupportedKey { get; set; } = new HashSet<UnsupportedTypeInHashSet>(); - public ISet<UnsupportedTypeInHashSet> UninstantiatedHashSetWithUnsupportedKey { get; set; } + public ISet<UnsupportedTypeInHashSet> UninstantiatedHashSetWithUnsupportedKey { get; set; } #if NETCOREAPP public IReadOnlySet<string> InstantiatedIReadOnlySet { get; set; } = new HashSet<string>(); @@ -348,7 +348,7 @@ namespace Microsoft.Extensions.Configuration.Binder.Test } public string Color { get; set; } - public int Length { get; set; } + public int Length { get; set; } } public class ImmutableLengthAndColorClass @@ -505,6 +505,113 @@ namespace Microsoft.Extensions.Configuration.Binder.Test Option2, } + public class CollectionsBindingWithErrorOnUnknownConfiguration + { + public class MyModelContainingArray + { + public TestSettingsEnum[] Enums { get; set; } + } + + public class MyModelContainingADictionary + { + public Dictionary<string, TestSettingsEnum> Enums { get; set; } + } + + [Fact] + public void WithFlagUnset_NoExceptionIsThrownWhenFailingToParseEnumsInAnArrayAndValidItemsArePreserved() + { + var dic = new Dictionary<string, string> + { + {"Section:Enums:0", "Option1"}, + {"Section:Enums:1", "Option3"}, // invalid - ignored + {"Section:Enums:2", "Option4"}, // invalid - ignored + {"Section:Enums:3", "Option2"}, + }; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + var configSection = config.GetSection("Section"); + + var model = configSection.Get<MyModelContainingArray>(o => o.ErrorOnUnknownConfiguration = false); + + Assert.Equal(2, model.Enums.Length); + Assert.Equal(TestSettingsEnum.Option1, model.Enums[0]); + Assert.Equal(TestSettingsEnum.Option2, model.Enums[1]); + } + + [Fact] + public void WithFlagUnset_NoExceptionIsThrownWhenFailingToParseEnumsInADictionaryAndValidItemsArePreserved() + { + var dic = new Dictionary<string, string> + { + {"Section:Enums:First", "Option1"}, + {"Section:Enums:Second", "Option3"}, // invalid - ignored + {"Section:Enums:Third", "Option4"}, // invalid - ignored + {"Section:Enums:Fourth", "Option2"}, + }; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + var configSection = config.GetSection("Section"); + + var model = configSection.Get<MyModelContainingADictionary>(o => + o.ErrorOnUnknownConfiguration = false); + + Assert.Equal(2, model.Enums.Count); + Assert.Equal(TestSettingsEnum.Option1, model.Enums["First"]); + Assert.Equal(TestSettingsEnum.Option2, model.Enums["Fourth"]); + } + + [Fact] + public void WithFlagSet_AnExceptionIsThrownWhenFailingToParseEnumsInAnArray() + { + var dic = new Dictionary<string, string> + { + {"Section:Enums:0", "Option1"}, + {"Section:Enums:1", "Option3"}, // invalid - exception thrown + {"Section:Enums:2", "Option1"}, + }; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + var configSection = config.GetSection("Section"); + + var exception = Assert.Throws<InvalidOperationException>( + () => configSection.Get<MyModelContainingArray>(o => o.ErrorOnUnknownConfiguration = true)); + + Assert.Equal( + SR.Format(SR.Error_GeneralErrorWhenBinding, nameof(BinderOptions.ErrorOnUnknownConfiguration)), + exception.Message); + } + + [Fact] + public void WithFlagSet_AnExceptionIsThrownWhenFailingToParseEnumsInADictionary() + { + var dic = new Dictionary<string, string> + { + {"Section:Enums:First", "Option1"}, + {"Section:Enums:Second", "Option3"}, // invalid - exception thrown + {"Section:Enums:Third", "Option1"}, + }; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + var configSection = config.GetSection("Section"); + + var exception = Assert.Throws<InvalidOperationException>( + () => configSection.Get<MyModelContainingADictionary>(o => + o.ErrorOnUnknownConfiguration = true)); + + Assert.Equal( + SR.Format(SR.Error_GeneralErrorWhenBinding, nameof(BinderOptions.ErrorOnUnknownConfiguration)), + exception.Message); + } + } + public record RootConfig(NestedConfig Nested); public record NestedConfig(string MyProp); @@ -804,7 +911,7 @@ namespace Microsoft.Extensions.Configuration.Binder.Test public class Foo { public IReadOnlyDictionary<string, int> Items { get; set; } = - new Dictionary<string, int> {{"existing-item1", 1}, {"existing-item2", 2}}; + new Dictionary<string, int> { { "existing-item1", 1 }, { "existing-item2", 2 } }; } @@ -829,7 +936,7 @@ namespace Microsoft.Extensions.Configuration.Binder.Test Assert.Equal(3, options.Items["item3"]); Assert.Equal(4, options.Items["item4"]); - + } [Fact] @@ -944,7 +1051,7 @@ namespace Microsoft.Extensions.Configuration.Binder.Test Assert.Equal(3, options.NonInstantiatedReadOnlyDictionary["item3"]); Assert.Equal(4, options.NonInstantiatedReadOnlyDictionary["item4"]); } - + [Fact] public void CanBindNonInstantiatedDictionaryOfISet() |