Skip to content

Configuration data allows nullable values, but ChainedConfigurationProvider.TryGet doesn't work correctly with them #65594

Open

Description

@eerhardt

See the conversation here: dotnet/core#7211 (comment).

IConfigurationProvider.TryGet(string key, out string? value); is nullable annotated as

This means that a ConfigurationProvider can have a null value for a key. So it would return true and the value == null.

However, ChainedConfigurationProvider doesn't seem to support this:

public bool TryGet(string key, out string? value)
{
value = _config[key];
return !string.IsNullOrEmpty(value);

If the inner configuration returned null, ChainedConfigurationProvider would return false, even though the it is possible the inner configuration provider returned true and value == null.

This feels like an issue with our API design here. Can null be a valid configuration value or not? If it can, should ChainedConfigurationProvider respect it?

cc @maxkoshevoi @halter73 @HaoK

Activity

  1. added this to the 7.0.0 milestone on Feb 18, 2022
  2. ghost added
    untriagedNew issue has not been triaged by the area owner
    on Feb 18, 2022
  3. maxkoshevoi commented on Feb 19, 2022

    @maxkoshevoi
    Contributor

    Found the reason of why out string? value wasn't annotated with [NotNullWhen(true)]: #57414 (comment)

  4. added and removed
    untriagedNew issue has not been triaged by the area owner
    on Feb 24, 2022
  5. halter73 commented on Mar 17, 2022

    @halter73
    Member

    I think the right behavior is for TryGet to return false if _config[key] is null but return true if its "". I'm not how much code out there today relies on falling back from chained config sources that return "". Hopefully not a lot.

  6. modified the milestones: 7.0.0, Future on Jul 14, 2022
  7. csnethaus commented on Apr 10, 2024

    @csnethaus

    I just ran into this behavior of the ChainedConfigurationProvider.

    We have a configuration system that get its values from a database, but all values can be overwritten per environment or secrets. Environment and secrets come from the ChainedConfigurationProvider, while the database values have their own IConfigurationProvider implementation.

    I wanted to overwrite one value with "" in the environment, but that did not happen due to the string.IsNullOrEmpty(value)check of theChainedConfigurationProvider`.

    I agree with @halter73 that "" should return true, but we also had an internal discussion that this would probably not be fixed in the near future as it could harm existing code. 🙄

  8. jamescrosswell commented on Jan 8, 2025

    @jamescrosswell

    Just adding a vote here.

    The current design results in a discrepancy between how Web Host and Generic Host read configuration data from appsettings.json files (presumably Generic Host uses ChainedConfigurationProvider). Web Host supports/returns empty strings verbatim as valid configuration values whereas Generic host interprets these as null.

    This breaks the Sentry SDK when using Generic Host (which allows you to set the URL of your sentry server to string.Empty to disable the SDK):

    Arguably the current behavioural inconsistencies are a bug then.

  9. rosebyte commented on Mar 3, 2026

    @rosebyte
    Member

    Triage: this issue seems related to other issues on the chained configuration source, we will investigate the root cause and potentially fix it.

  10. self-assigned this
    on Jul 17, 2026
  11. added
    in-prThere is an active PR which will close this issue when it is merged
    on Jul 28, 2026
  12. rosebyte commented on Aug 5, 2026

    @rosebyte
    Member

    Part of this issue was solved by #131480. What's left is that the ChainedConfigurationProvider can't distinguish between explicit null and missing value, since it reads IConfiguration where the two are both represented by null returned. What we can do collapses into two things:

    We can unwrap our IConfiguration implementations, namely ConfigurationRoot and ConfigurationManager and iterate their providers (we discussed this point with @tarekgh in the mentioned PR and there is even a precedent of doing it in ConfigurationSection). Relatively easy and quick, however, we create a situation where 3rd party IConfiguration implementors won't be able to contribute to this behaviour because they can't implement IConfigurationRoot (and because it would be dangerous if we bypassed their IConfiguration indexer by iterating providers directly when we don't know if there is any additional logic in the indexer and we would break existing code). Threrefore, we should provide the way by adding a new interface they can implement:

    public interface IConfigurationQuery
    {
        bool TryGet(string key, out string? value);
        IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string? parentPath);
    }

    If the 3rd party IConfiguration doesn't implement the interface, we fallback to interpreting null as missing key, which is what we do now so there shouldn't be any breaking change introduced.

    What do you think?

  13. rosebyte commented on Aug 5, 2026

    @rosebyte
    Member

    Removing the bug label. The half of this that was genuinely a defect, an empty string being reported as absent, was fixed in #131480. What's left is that IConfiguration exposes only an indexer, so a chained configuration cannot distinguish "key absent" from "key present with a null value". That's a limitation of the abstraction rather than something ChainedConfigurationProvider gets wrong, and as @eerhardt put it originally, the real question is whether null is a valid configuration value at all.

  14. added
    api-suggestionEarly API idea and discussion, it is NOT ready for implementation
    and removed
    in-prThere is an active PR which will close this issue when it is merged
    on Aug 5, 2026
  15. removed their assignment
    on Aug 5, 2026
  16. tarekgh commented on Aug 5, 2026

    @tarekgh
    Member

    Looks good to me. One small tweak could make it easier to extend the functionality in the future without having to modify the interface or worry about down-level support. That said, it's a minor detail, and I think the overall direction makes sense.

    public interface IConfigurationQueryProvider
        {
            /// <summary>
            /// Returns an object that answers full-fidelity queries against this configuration,
            /// or <see langword="null"/> if not supported.
            /// </summary>
            ConfigurationQuery? GetConfigurationQuery();
        }
    
        /// <summary>
        /// Answers full-fidelity queries against a configuration. This is an abstract class rather
        /// than an interface so new query operations can be added in future versions as virtual
        /// members with default implementations, without breaking existing implementers.
        /// </summary>
        public abstract class ConfigurationQuery
        {
            public abstract bool TryGet(string key, out string? value);
            public abstract IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string? parentPath);
    
            // Future capabilities are added here as VIRTUAL methods with a default body:
        }

    CC @halter73. Note, this is something to support in the future and not for .NET 11.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions