Skip to content

[API Proposal]: Allow keys with colons in config #67616

Open
@SteveDunn

Description

@SteveDunn

Background and motivation

As described in issue #42643, some users want to be able to have configuration keys that contain a colon character.

However, the colon character is the default separator for config keys, so if I had a config key named https://google.es, that would be translated to a composite key, the first being https, and the second being //google.es.

The purpose of this API suggestion is to enable users to specify their own separator character for configuration so that they can have keys containing colons, or any other character they wish.

API Proposal

The API changes are listed in the PR: https://github.com/dotnet/runtime/pull/66886/files#diff-ec34a58f9fd18f4d4fccabf4efc22dd555b9d32963b26419ed107c872b67356f .

We want overloads to provide the separator character:

public static partial class JsonConfigurationExtensions
{
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Microsoft.Extensions.FileProviders.IFileProvider? provider, string path, bool optional, bool reloadOnChange) { throw null; }
+   public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Microsoft.Extensions.FileProviders.IFileProvider? provider, string path, bool optional, bool reloadOnChange, string separator = ":") { throw null; }
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, System.Action<Microsoft.Extensions.Configuration.Json.JsonConfigurationSource>? configureSource) { throw null; }
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path) { throw null; }
+   public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, string separator) { throw null; }
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional) { throw null; }
+   public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional, string separator) { throw null; }
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange) { throw null; }
+   public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange, string separator) { throw null; }
    public static IConfigurationBuilder AddJsonStream(this IConfigurationBuilder builder, System.IO.Stream stream) { throw null; }
+   public static IConfigurationBuilder AddJsonStream(this IConfigurationBuilder builder, System.IO.Stream stream, string separator = ":") { throw null; }
}

ConfigurationPath.cs would have a new method to complement Combine, named CombineWith:

  public static string Combine(params string[] pathSegments) { throw null; }
+ public static string CombineWith(string separator, params string[] pathSegments) { throw null; }

... and a new method to complement GetSectionKey, named GetSectionKeyWith:

  public static string? GetSectionKey(string? path) { throw null; }
+ public static string? GetSectionKeyWith(string separator, string? path) { throw null; }

ConfigurationKeyComparer.cs will have an overloaded constructor to specify the separator:

public partial class ConfigurationKeyComparer : System.Collections.Generic.IComparer<string>
{
        public ConfigurationKeyComparer() { }
+       public ConfigurationKeyComparer(string separator) { }
        public static Microsoft.Extensions.Configuration.ConfigurationKeyComparer Instance { get { throw null; } }
        public static Microsoft.Extensions.Configuration.ConfigurationKeyComparer GetInstanceFor(string separator){ throw null; }
        public int Compare(string? x, string? y) { throw null; }
}

ConfigurationProvider.cs will have a new method to expose the key separator that it's using:

public abstract partial class ConfigurationProvider : Microsoft.Extensions.Configuration.IConfigurationProvider
{
    protected ConfigurationProvider() { }
+   public virtual string GetDelimiter() { throw null; }
    protected System.Collections.Generic.IDictionary<string, string?> Data { get { throw null; } set { } }
    public virtual System.Collections.Generic.IEnumerable<string> GetChildKeys(System.Collections.Generic.IEnumerable<string> earlierKeys, string? parentPath) { throw null; }
    public Microsoft.Extensions.Primitives.IChangeToken GetReloadToken() { throw null; }

ConfigurationSection.cs will have an overload that takes the separator:

public partial class ConfigurationSection : Microsoft.Extensions.Configuration.IConfiguration, Microsoft.Extensions.Configuration.IConfigurationSection
{
        public ConfigurationSection(Microsoft.Extensions.Configuration.IConfigurationRoot root, string path,) { }
+       public ConfigurationSection(Microsoft.Extensions.Configuration.IConfigurationRoot root, string path, string separator = ":") { }
        public string? this[string key] { get { throw null; } set { } }
        ...
}

API Usage

Taken from this test, if we had some JSON config:

{
    "auths": {
        "http://google": {
            "uri": "https://www.google.es"
        },
        "http://microsoft": {
            "uri": "https://www.microsoft.es"
        }
    }
}

We could specify a different separator when loading it, e.g. a backtick (`), as that is different to the default separator of colon:

public class MyClass
{
    public Dictionary<string, OtherType> Auths { get; set; }
}

public class OtherType
{
    public string Uri { get; set; }
}

var config = new ConfigurationBuilder()
    .AddJsonFile("json_with_colons_in_keys.json", optional: false, reloadOnChange: true, separator: "`").Build();

var settings = new MyClass();

config.Bind(settings);
Assert.Equal("https://www.google.es", settings.Auths["http://google"].Uri);

Alternative Designs

No response

Risks

I can't see any risks. The default separator is colon (:), so the changes are backwards compatible.

The PR I did for this Issue has this new functionality, and all of the existing tests still pass, without modification.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions