Skip to content

Commit 067370c

Browse files
authored
Fix binding options against null values (#1663)
* Fix binding options against null values * Added test for empty string
1 parent 58a177b commit 067370c

5 files changed

Lines changed: 79 additions & 6 deletions

File tree

src/Configuration/src/Abstractions/CompositeConfigurationProvider.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,13 @@ public virtual bool TryGet(string key, out string? value)
8484

8585
LogTryGet(GetType().Name, key);
8686

87-
value = ConfigurationRoot?.GetValue<string>(key);
88-
bool found = value != null;
87+
if (ConfigurationRoot == null)
88+
{
89+
value = null;
90+
return false;
91+
}
92+
93+
bool found = InnerTryGet(ConfigurationRoot, key, out value);
8994

9095
if (found)
9196
{
@@ -95,6 +100,31 @@ public virtual bool TryGet(string key, out string? value)
95100
return found;
96101
}
97102

103+
private static bool InnerTryGet(IConfigurationRoot root, string key, out string? value)
104+
{
105+
IList<IConfigurationProvider> providers = root.Providers as IList<IConfigurationProvider> ?? root.Providers.ToList();
106+
107+
for (int index = providers.Count - 1; index >= 0; index--)
108+
{
109+
IConfigurationProvider provider = providers[index];
110+
111+
try
112+
{
113+
if (provider.TryGet(key, out value))
114+
{
115+
return true;
116+
}
117+
}
118+
catch (ObjectDisposedException)
119+
{
120+
// Skip disposed providers to avoid exceptions during access.
121+
}
122+
}
123+
124+
value = null;
125+
return false;
126+
}
127+
98128
public void Set(string key, string? value)
99129
{
100130
ArgumentNullException.ThrowIfNull(key);

src/Configuration/src/Encryption/DecryptionConfigurationProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public override bool TryGet(string key, out string? value)
4646
}
4747
}
4848

49-
return value != null;
49+
return found;
5050
}
5151

5252
private ITextDecryptor EnsureDecryptor(IConfigurationRoot configurationRoot)

src/Configuration/src/Placeholder/PlaceholderConfigurationProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public override bool TryGet(string key, out string? value)
3636
}
3737
}
3838

39-
return value != null;
39+
return found;
4040
}
4141

4242
[LoggerMessage(Level = LogLevel.Trace, Message = "Replaced value '{OriginalValue}' at key '{Key}' with '{ReplacementValue}'.")]

src/Configuration/test/Placeholder.Test/PlaceholderConfigurationTest.cs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ public void Substitutes_placeholders_in_key_lookups()
205205
["key2"] = "${key1?not-found}",
206206
["key3"] = "${no-key?not-found}",
207207
["key4"] = "${no-key}",
208-
["key5"] = string.Empty
208+
["key5"] = string.Empty,
209+
["key6"] = null
209210
};
210211

211212
var builder = new ConfigurationBuilder();
@@ -219,6 +220,8 @@ public void Substitutes_placeholders_in_key_lookups()
219220
configuration["key3"].Should().Be("not-found");
220221
configuration["key4"].Should().Be("${no-key}");
221222
configuration["key5"].Should().BeEmpty();
223+
configuration["key6"].Should().BeNull();
224+
configuration["key7"].Should().BeNull();
222225

223226
configuration["no-key"] = "new-key-value";
224227

@@ -408,6 +411,46 @@ static void AssertTypesInSourceTree(IList<IConfigurationSource> sources, int ind
408411
}
409412
}
410413

414+
[Fact]
415+
public void Binding_property_against_null_overwrites_default_value()
416+
{
417+
var appSettings = new Dictionary<string, string?>
418+
{
419+
["Root:TestOptions:Value"] = null
420+
};
421+
422+
var builder = new ConfigurationBuilder();
423+
builder.AddInMemoryCollection(appSettings);
424+
builder.AddPlaceholderResolver();
425+
IConfigurationRoot configuration = builder.Build();
426+
427+
IConfigurationSection section = configuration.GetSection("Root:TestOptions");
428+
var options = section.Get<TestOptions>();
429+
430+
options.Should().NotBeNull();
431+
options.Value.Should().BeNull();
432+
}
433+
434+
[Fact]
435+
public void Binding_property_against_empty_string_overwrites_default_value()
436+
{
437+
var appSettings = new Dictionary<string, string?>
438+
{
439+
["Root:TestOptions:Value"] = string.Empty
440+
};
441+
442+
var builder = new ConfigurationBuilder();
443+
builder.AddInMemoryCollection(appSettings);
444+
builder.AddPlaceholderResolver();
445+
IConfigurationRoot configuration = builder.Build();
446+
447+
IConfigurationSection section = configuration.GetSection("Root:TestOptions");
448+
var options = section.Get<TestOptions>();
449+
450+
options.Should().NotBeNull();
451+
options.Value.Should().BeEmpty();
452+
}
453+
411454
public void Dispose()
412455
{
413456
_loggerFactory.Dispose();

src/Configuration/test/Placeholder.Test/TestOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ namespace Steeltoe.Configuration.Placeholder.Test;
66

77
internal sealed class TestOptions
88
{
9-
public string? Value { get; set; }
9+
public string? Value { get; set; } = "DefaultValue";
1010
}

0 commit comments

Comments
 (0)