Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Tracked by #44517 ...
SteveDunn opened this issue Apr 5, 2022 · 12 comments
Open
Tracked by #44517 ...

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

SteveDunn opened this issue Apr 5, 2022 · 12 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-Extensions-Configuration
Milestone

Comments

@SteveDunn
Copy link
Contributor

SteveDunn commented Apr 5, 2022

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.

@SteveDunn SteveDunn added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Apr 5, 2022
@dotnet-issue-labeler dotnet-issue-labeler bot added area-Extensions-Configuration untriaged New issue has not been triaged by the area owner labels Apr 5, 2022
@ghost
Copy link

ghost commented Apr 5, 2022

Tagging subscribers to this area: @dotnet/area-extensions-configuration
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

Author: SteveDunn
Assignees: -
Labels:

api-suggestion, untriaged, area-Extensions-Configuration

Milestone: -

@maryamariyan maryamariyan removed the untriaged New issue has not been triaged by the area owner label Apr 5, 2022
@maryamariyan maryamariyan added this to the 7.0.0 milestone Apr 5, 2022
@SteveDunn
Copy link
Contributor Author

@maryamariyan - is there any update on this one?

@eerhardt
Copy link
Member

This isn't planned for .NET 7 according to #64015. We can consider it in the future.

@eerhardt eerhardt modified the milestones: 7.0.0, Future Jun 21, 2022
@SteveDunn
Copy link
Contributor Author

SteveDunn commented Jun 28, 2022

This isn't planned for .NET 7 according to #64015. We can consider it in the future.

@eerhardt - I think it is. That page (#64015) links to #44517

Built-in configuration providers can do more robust key handling

That page links to Bug #42643

ConfigurationBuilder - Configuration.Json: Binding a Dictionary<string,string> error when Key contains an string url like http://www.google.es

That has a PR #66886.

That PR is closed until this API Proposal is accepted.

@eerhardt
Copy link
Member

Thanks @SteveDunn, I had missed that one.

I'll move it back to 7.0 for now, but feature complete is in 2 weeks, so I'm not confident this will make it for 7.0.

@eerhardt eerhardt modified the milestones: Future, 7.0.0 Jun 28, 2022
@eerhardt
Copy link
Member

The "Built-in configuration providers can do more robust key handling" feature #44517 has been moved out of 7.0.

We will consider this in a future release. Moving this issue out of the 7.0 milestone as well.

@SteveDunn
Copy link
Contributor Author

Glad to see this one back.

@layomia
Copy link
Contributor

layomia commented Jul 21, 2023

Inlining a user scenario for this API proposal where it is desired for string dictionary keys - #42643.

@layomia
Copy link
Contributor

layomia commented Jul 21, 2023

Triage: moving to future given we're in a late period in the release & this would need API review.

@layomia layomia modified the milestones: 8.0.0, Future Jul 21, 2023
@SteveDunn
Copy link
Contributor Author

@layomia - is this being considered for .NET 9?

@julealgon
Copy link

The way this issue is named feels a bit misleading/reductive to me. Could someone consider a rename to something like:

[API Proposal]: Support configurable section delimiter in IConfiguration

?

@SteveDunn
Copy link
Contributor Author

@layomia - is this being considered for .NET 9?

.net 10 then?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-Extensions-Configuration
Projects
None yet
Development

No branches or pull requests

5 participants