Description
Background and motivation
Currently, the configuration binder does not differentiate between null
and missing values—if a configuration value is explicitly set to null
, it's treated as if it doesn't exist.
To enable this distinction, we're introducing a new API in the ConfigurationSection
class. This API will return the configuration value and explicitly indicate whether the value is present in the configuration or truly missing.
API Proposal
namespace Microsoft.Extensions.Configuration
public class ConfigurationSection : IConfigurationSection
{
public virtual bool TryGetValue(string? key, out string? value);
}
API Usage
The new API is intended to be used internally by the configuration binder, so user code—such as the example below—won’t require any changes but will start behaving as expected:
string jsonConfig = @"
{
""NullConfiguration"": {
""StringProperty1"": ""New Value!"",
""StringProperty2"": null,
""StringProperty3"": """",
""IntProperty1"": 42,
""IntProperty2"": null,
},
}";
var configuration = new ConfigurationBuilder()
.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(jsonConfig)))
.Build().GetSection("NullConfiguration");
NullConfiguration result = configuration.Get<NullConfiguration>();
public class NullConfiguration
{
public NullConfiguration()
{
// Initialize with non-default value to ensure binding will override these values
StringProperty1 = "Initial Value 1";
StringProperty2 = "Initial Value 2";
StringProperty3 = "Initial Value 3";
IntProperty1 = 123;
IntProperty2 = 456;
}
public string? StringProperty1 { get; set; }
public string? StringProperty2 { get; set; }
public string? StringProperty3 { get; set; }
public int? IntProperty1 { get; set; }
public int? IntProperty2 { get; set; }
}
Details
Today, the binder relies on IConfigurationSection
, which does not indicate whether a null return value means the key exists with a null value or is entirely missing. Since we support netfx
and netstandard2.0
, extending the interface would introduce a breaking change for those platforms which we need to avoid.
To solve this:
- We’ll enhance the existing public
ConfigurationSection
class (which implementsIConfigurationSection
) with the new functionality. - The binder will detect if the configuration object is a
ConfigurationSection
and use the new capability when available. - For custom implementations of
IConfigurationSection
, consumers can subclassConfigurationSection
to gain the new behavior. - If needed in the future, we still have the option to introduce a new interface or abstract base class.
Alternative Designs
One option was to expose a new abstract class that extends IConfigurationSection
and includes the new API, then have ConfigurationSection
inherit from it. However, we prefer to add the new API directly to ConfigurationSection
itself, rather than introducing a new class.