diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/BinderOptions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/BinderOptions.cs
index f5ffd6eb5758f8..b4664675b15dfb 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 @@ public class BinderOptions
public bool BindNonPublicProperties { get; set; }
///
- /// 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 is thrown with a description
- /// of the missing properties.
+ /// of the error.
///
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 1d0992781f08e6..332f74ef794b31 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 @@ private static void BindConcreteDictionary(
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 @@ private static void BindCollection(
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 @@ private static Array BindArray(Type type, IEnumerable? source, IConfiguration co
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 @@ private static Array BindArray(Type type, IEnumerable? source, IConfiguration co
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 197b325252ef91..a926fc42386a9b 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 @@
Failed to create instance of type '{0}'.
+
+ '{0}' was set and binding has failed. The likely cause is an invalid configuration value.
+
'{0}' was set on the provided {1}, but the following properties were not found on the instance of {2}: {3}
diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs
index a24e7d3b9a3518..51a25429f77e83 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 @@ public string ReadOnly
public ISet ISetNoSetter { get; } = new HashSet();
public HashSet InstantiatedHashSetWithSomeValues { get; set; } =
- new HashSet(new[] {"existing1", "existing2"});
+ new HashSet(new[] { "existing1", "existing2" });
public SortedSet InstantiatedSortedSetWithSomeValues { get; set; } =
- new SortedSet(new[] {"existing1", "existing2"});
+ new SortedSet(new[] { "existing1", "existing2" });
public SortedSet NonInstantiatedSortedSetWithSomeValues { get; set; } = null!;
public ISet InstantiatedISetWithSomeValues { get; set; } =
new HashSet(new[] { "existing1", "existing2" });
-
+
public ISet HashSetWithUnsupportedKey { get; set; } =
new HashSet();
- public ISet UninstantiatedHashSetWithUnsupportedKey { get; set; }
+ public ISet UninstantiatedHashSetWithUnsupportedKey { get; set; }
#if NETCOREAPP
public IReadOnlySet InstantiatedIReadOnlySet { get; set; } = new HashSet();
@@ -348,7 +348,7 @@ public MutableStructWithConstructor(string randomParameter)
}
public string Color { get; set; }
- public int Length { get; set; }
+ public int Length { get; set; }
}
public class ImmutableLengthAndColorClass
@@ -505,6 +505,113 @@ public enum TestSettingsEnum
Option2,
}
+ public class CollectionsBindingWithErrorOnUnknownConfiguration
+ {
+ public class MyModelContainingArray
+ {
+ public TestSettingsEnum[] Enums { get; set; }
+ }
+
+ public class MyModelContainingADictionary
+ {
+ public Dictionary Enums { get; set; }
+ }
+
+ [Fact]
+ public void WithFlagUnset_NoExceptionIsThrownWhenFailingToParseEnumsInAnArrayAndValidItemsArePreserved()
+ {
+ var dic = new Dictionary
+ {
+ {"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(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
+ {
+ {"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(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
+ {
+ {"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(
+ () => configSection.Get(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
+ {
+ {"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(
+ () => configSection.Get(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 @@ public void CanBindInstantiatedDictionaryOfIReadOnlySetWithSomeExistingValues()
public class Foo
{
public IReadOnlyDictionary Items { get; set; } =
- new Dictionary {{"existing-item1", 1}, {"existing-item2", 2}};
+ new Dictionary { { "existing-item1", 1 }, { "existing-item2", 2 } };
}
@@ -829,7 +936,7 @@ public void CanBindInstantiatedReadOnlyDictionary2()
Assert.Equal(3, options.Items["item3"]);
Assert.Equal(4, options.Items["item4"]);
-
+
}
[Fact]
@@ -944,7 +1051,7 @@ public void CanBindNonInstantiatedReadOnlyDictionary()
Assert.Equal(3, options.NonInstantiatedReadOnlyDictionary["item3"]);
Assert.Equal(4, options.NonInstantiatedReadOnlyDictionary["item4"]);
}
-
+
[Fact]
public void CanBindNonInstantiatedDictionaryOfISet()