Skip to content

Commit

Permalink
[release/8.0-rc2] Ensure Bind can handle null from GetSection (#92477)
Browse files Browse the repository at this point in the history
* Ensure Bind can handle null from GetSection

IConfiguration instances may return a null value from GetSection.
We were not handling this and would throw a NullReferenceException.

* Address feedback

* Remove Moq from ConfigBinder tests

---------

Co-authored-by: Eric StJohn <[email protected]>
  • Loading branch information
github-actions[bot] and ericstj authored Sep 22, 2023
1 parent 873b3cc commit 974edf9
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,11 @@ private static void BindInstance(
return;
}

if (config is null)
{
return;
}

var section = config as IConfigurationSection;
string? configValue = section?.Value;
if (configValue != null && TryConvertValue(type, configValue, section?.Path, out object? convertedValue, out Exception? error))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -888,5 +888,11 @@ public int MyIntProperty
}
}

public class SimplePoco
{
public string A { get; set; }
public string B { get; set; }
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#if BUILDING_SOURCE_GENERATOR_TESTS
using Microsoft.Extensions.Configuration;
#endif
using Microsoft.Extensions.Configuration.Memory;
using Microsoft.Extensions.Configuration.Test;
using Xunit;

Expand Down Expand Up @@ -1767,7 +1768,7 @@ public void EnsureCallingThePropertySetter()
Assert.Equal(0, options.OtherCodeNullable);
Assert.Equal("default", options.OtherCodeString);
Assert.Null(options.OtherCodeNull);
Assert.Null(options.OtherCodeUri);
Assert.Null(options.OtherCodeUri);
}

[Fact]
Expand Down Expand Up @@ -2238,7 +2239,7 @@ void TestUntypedOverloads(IConfiguration? configuration, string? key)
Assert.Throws<ArgumentNullException>(() => configuration.GetValue(typeof(GeolocationClass), key, new GeolocationClass()));
Assert.Throws<ArgumentNullException>(() => configuration.GetValue(typeof(Geolocation), key));
Assert.Throws<ArgumentNullException>(() => configuration.GetValue(typeof(Geolocation), key, defaultValue: null));
Assert.Throws<ArgumentNullException>(() => configuration.GetValue(typeof(Geolocation), key, default(Geolocation)));
Assert.Throws<ArgumentNullException>(() => configuration.GetValue(typeof(Geolocation), key, default(Geolocation)));
}
}

Expand Down Expand Up @@ -2404,5 +2405,38 @@ public void SharedChildInstance()
config.GetSection("A").Bind(instance);
Assert.Equal("localhost", instance.ConnectionString);
}

[Fact]
public void CanBindToMockConfigurationSection()
{
const string expectedA = "hello";

var configSource = new MemoryConfigurationSource()
{
InitialData = new Dictionary<string, string?>()
{
[$":{nameof(SimplePoco.A)}"] = expectedA,
}
};
var configRoot = new MockConfigurationRoot(new[] { configSource.Build(null) });
var configSection = new ConfigurationSection(configRoot, string.Empty);

SimplePoco result = new();
configSection.Bind(result);

Assert.Equal(expectedA, result.A);
Assert.Equal(default(string), result.B);
}

// a mock configuration root that will return null for undefined Sections,
// as is common when Configuration interfaces are mocked
class MockConfigurationRoot : ConfigurationRoot, IConfigurationRoot
{
public MockConfigurationRoot(IList<IConfigurationProvider> providers) : base(providers)
{ }

IConfigurationSection IConfiguration.GetSection(string key) =>
this[key] is null ? null : new ConfigurationSection(this, key);
}
}
}

0 comments on commit 974edf9

Please sign in to comment.