Skip to content

Commit

Permalink
Control if invalid configuration values are swallowed or thrown (#77708)
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveDunn committed Nov 6, 2022
1 parent fb001c4 commit 264d739
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ public class BinderOptions
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; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
}
Expand Down Expand Up @@ -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);
}

}
}
}
Expand Down Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,20 @@ public string ReadOnly
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>();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -505,6 +505,113 @@ public enum TestSettingsEnum
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);
Expand Down Expand Up @@ -804,7 +911,7 @@ public void CanBindInstantiatedDictionaryOfIReadOnlySetWithSomeExistingValues()
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 } };

}

Expand All @@ -829,7 +936,7 @@ public void CanBindInstantiatedReadOnlyDictionary2()
Assert.Equal(3, options.Items["item3"]);
Assert.Equal(4, options.Items["item4"]);


}

[Fact]
Expand Down Expand Up @@ -944,7 +1051,7 @@ public void CanBindNonInstantiatedReadOnlyDictionary()
Assert.Equal(3, options.NonInstantiatedReadOnlyDictionary["item3"]);
Assert.Equal(4, options.NonInstantiatedReadOnlyDictionary["item4"]);
}


[Fact]
public void CanBindNonInstantiatedDictionaryOfISet()
Expand Down

0 comments on commit 264d739

Please sign in to comment.