-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Enable removal of a key or subtree #36543
Comments
+1 MSBuild Transforms have this |
Would be a breaking API change. |
It would be now... But an additional interface could be added with that method and sources could optionally support it. |
Yep! We'll look at this in the future. |
We are not planning on changing the interface but feel free to submit a PR with this implemented as an extension method. |
I don't think you can do this with an extension method unless you allow the extension method to cast (internally) all of the abstractions down to the concrete implementations and start poking around, which doesn't seem like the greatest idea. That would be necessary because none of the abstractions like As it stands, the list of providers would have to be made public on Without something to indicate whether the source supports key removal it may be hard to implement as an extension method. Some sort of public interface ISupportKeyRemoval
{
void Remove(string key);
}
Then the extension method could totally work. It could cast the inbound object as Does that sound reasonable? |
It'd be nice if |
@tillig what I had in mind when @HaoK and I discussed this is that an extension method could recursively walk down the tree (probably using the existing This already sets the value for that key to null on all underlying providers, so you probably wouldn't need access to list of providers just for this. That said, in the current implementation of our providers setting the value of a section to null just sets the value for that key in an internal dictionary to null. Subsequent calls to I think we can discuss/test changing the behavior of setting a null value to remove the entry from the dictionary instead of nulling the value. At least right now I cannot think of any reason that would be fundamentally bad and I cannot think of any way returning dangling null sections that contain null values is useful. My main concern with doing that is that the contract of the interface might be too loose and if third party providers don't comply with this behavior the |
The problem with just nulling things out is that if you want to back configuration with something like Redis or another persistence layer, storing "null" may actually be valid... as opposed to actually removing the keys, which was the intent of the difference here. This is like the equivalent of a |
@tillig however |
It doesn't seem any analogy works. If you consider it an arbitrary data source, it has CRU - Create, Read, Update... but no Delete. It just seems incomplete. Hence the desire to make it complete. I found in my own dealings that managing configuration through this thing - not reading, but actually also writing (which is supported) - becomes problematic when you get outside the very simple use cases of the original out-of-the-box providers (XML / JSON / environment). It seems a shame to not add some sort of way to opt in to allow for a complete configuration manager story. |
@tillig FWIW, the Create operation doesn't exist in The following test attempts to demonstrate why it is desirable that setting a key to null removes it from the tree reachable through For brevity I used the [Fact]
public void ConfigurationRemoveTest()
{
var config = new ConfigurationBuilder()
.AddInMemoryCollection()
.Build();
var section = config.GetSection("a:b:c"); // represents an arbitrary child key in the key-space
Assert.Null(section.Value); // does not have a value
Assert.False(section.Exists()); // hence it doesn't exists yet
Assert.Equal(config.AsEnumerable().Count(), 0); // in fact the configuration is still empty
section.Value = "x"; // same as config["a:b:c"] = "x";
Assert.True(config.GetSection("a").Exists()); // the grand parent exists because it has a grand child
Assert.True(config.GetSection("a:b").Exists()); // the parent exists because it has a child
Assert.True(config.GetSection("a:b:c").Exists()); // and the child exists because it has a value
// Unfortunately things start getting inconsistent if you try to set the value to null
config["a:b:c"] = null;
Assert.True(config.GetSection("a").Exists()); // the grand parent exists because it has a grand child
Assert.True(config.GetSection("a:b").Exists()); // the parent exists because it has a child
Assert.False(config.GetSection("a:b:c").Exists()); // but the child doesn't actually exist!!!
} |
@divega The "Create" part is what you did in the test by setting a value for a section that didn't previously exist. That's sort of how it works in dictionary land, too - you don't have to call However, I think I'm starting to understand what you're saying. The inconsistency you point out is part of what I want to avoid; but also that adds the natural implications that "setting to null == removal" as well as "you can't store a null in configuration"; which is to say, even if I have a JSON configuration file like this...
...then changing the semantics to "null == removed" would imply that reading the null value (and any child null values) would just be skipped or ignored. It would also mean this yields an empty configuration:
...which is fine, again, as long as the semantics change to be "a tree of things in which all values are null is effectively removed from configuration." Providers could deal with that reasonably. Is that what you're getting at? If so, it would mean the // This should be false, but is currently inconsistently/incorrectly true.
Assert.True(config.GetSection("a").Exists());
// Same here.
Assert.True(config.GetSection("a:b").Exists());
// The leaf is the only one correctly reporting.
Assert.False(config.GetSection("a:b:c").Exists()); However, if that's not what you're saying then maybe I'm still misunderstanding. What I want is the ability to get the consistent results out of that unit test. Ideally, the actual call to |
@tillig I agree the indexer in And yes, that is what I am getting at. I am still not sure it is feasible 😄, but I think it would be more consistent. |
Excellent. If the semantics can be updated such that tree-of-nulls == removed and is reported that way throughout (e.g., through |
Possibly. But I haven't tried just changing the implementation of |
@tillig oh and by all means, feel free to experiment with this yourself if you would like to spend a PR. |
I would like to +1 the request to support
And you want to remove an item with:
you will end up with an array which has two items: "item1" and null Being able to remove an entire subtree or individual keys would be very helpful here. If you want more details on my scenario (why I want to remove items from IConfigurationRoot) feel free to let me know. |
How about we change this from |
I just stumbled on this issue with a very real use case. In my {
"Kestrel": {
"Certificates": {
"Default": {
"Path": "<path to .pfx file>",
"Password": "$CREDENTIAL_PLACEHOLDER$"
}
}
}
} During development, I'd like to use the ASP.NET Core HTTPS development certificate but I can't remove the Unfortunately I still haven't found a workaround to this issue. Edit I figured out how to use the ASP.NET Core HTTPS development certificate for development by overriding the {
"Kestrel": {
"Certificates": {
"Default": {
"Path": "",
"Password": ""
}
}
}
} But that's just luck because of how the certificate configuration loader works. |
Tee `PlatformNotSupportedException` is problematic when the EventLog sink is configured through [Serilog.Settings.Configuration][1], where it's impossible to conditionally enable on Windows and disable on other platforms. Real scenario: the production app will run on Windows and the appsettings configuration looks like this (using TOML instead of JSON): ```json [Serilog.WriteTo.EventLogSink] Name = 'EventLog' Args = { source = 'MyApplication', manageEventSource = true, restrictedToMinimumLevel = 'Warning' } ``` When developing on Linux or macOS, it's [impossible to remove][2] the EventLog configuration so it's better for this sink to be ignored since EventLog doesn't exist on these platforms anyway. Note: this is exactly how the [Notepad sink][3] works and it's a good solution. [1]: https://github.com/serilog/serilog-settings-configuration [2]: dotnet/runtime#36543 [3]: https://github.com/serilog-contrib/serilog-sinks-notepad
Tee `PlatformNotSupportedException` is problematic when the EventLog sink is configured through [Serilog.Settings.Configuration][1], where it's impossible to conditionally enable on Windows and disable on other platforms. Real scenario: the production app will run on Windows and the appsettings configuration looks like this (using TOML instead of JSON): ```toml [Serilog.WriteTo.EventLogSink] Name = 'EventLog' Args = { source = 'MyApplication', manageEventSource = true, restrictedToMinimumLevel = 'Warning' } ``` When developing on Linux or macOS, it's [impossible to remove][2] the EventLog configuration so it's better for this sink to be ignored since EventLog doesn't exist on these platforms anyway. Note: this is exactly how the [Notepad sink][3] works and it's a good solution. [1]: https://github.com/serilog/serilog-settings-configuration [2]: dotnet/runtime#36543 [3]: https://github.com/serilog-contrib/serilog-sinks-notepad
The `PlatformNotSupportedException` is problematic when the EventLog sink is configured through [Serilog.Settings.Configuration][1], where it's impossible to conditionally enable on Windows and disable on other platforms. Real scenario: the production app will run on Windows and the appsettings configuration looks like this (using TOML instead of JSON): ```toml [Serilog.WriteTo.EventLogSink] Name = 'EventLog' Args = { source = 'MyApplication', manageEventSource = true, restrictedToMinimumLevel = 'Warning' } ``` When developing on Linux or macOS, it's [impossible to remove][2] the EventLog configuration so it's better for this sink to be ignored since EventLog doesn't exist on these platforms anyway. Note: this is exactly how the [Notepad sink][3] works and it's a good solution. [1]: https://github.com/serilog/serilog-settings-configuration [2]: dotnet/runtime#36543 [3]: https://github.com/serilog-contrib/serilog-sinks-notepad
+1 on a very similar use case. I had to recreate HTTPS certificates and found the best way of doing that would be to temporarily allow the website to run in HTTP mode. Preferably I would have liked to accomplish this with a single flag to adjust various settings and middlewares throughout, but I couldn't find a way to just wipe out the https endpoint without going to my pipeline files to clear the env variables. |
This "cheat" works:
|
While you can set a value for a key, if you want to remove an entire subtree of configuration settings the best you can do right now is to recursively iterate through all keys and subkeys and set every value found to null.
It would be nice to have a remove method to allow complete removal of keys.
I imagine it would be on the
IConfiguration
interface:As well as on the
IConfigurationSource
interfaceImplementation of the
Remove
method would be similar to the implementation of theSet
method forMicrosoft.Extensions.Configuration.ConfigurationSection
where it would iterate through each provider and callRemove
. Providers could each do the appropriate thing as needed to remove the specified key and all children from the source.A use case this addresses is for providing an administrative interface to modify configuration. Users in the interface may need to add new keys or rename existing ones - you can't rename keys right now because there's no way to delete the old keys; only set the values to null. For implementations of
IConfigurationProvider
with a persistent backing store, this makes for a very messy and unmaintainable solution long-term.The text was updated successfully, but these errors were encountered: