XML attribute is read as a null IConfigurationSection.Value starting in 6.0.0
Description
I have an XML configuration file like this:
<configuration>
<Level1>
<Level2 Key1="Value1" />
<Level2 Key2="Value2" />
</Level1>
</configuration>
that I read using Microsoft.Extensions.Configuration.IConfigurationSection:
foreach (var configItem in configRoot.GetSection("Level1:Level2").GetChildren())
Console.WriteLine("Path={0}, key={1}, value={2}", configItem.Path, configItem.Key, configItem.Value ?? "<null>");
Up to v5.0.0 it returned the expected keys and values, but from v6.0.0 returns "0" and "1" for keys and nulls for values.
Reproduction Steps
Expected behavior
M.E.Configuration version 6.0.0.0
Path=Level1:Level2:Key1, key=Key1, value=Value1
Path=Level1:Level2:Key2, key=Key2, value=Value2
Actual behavior
M.E.Configuration version 6.0.0.0
Path=Level1:Level2:0, key=0, value=<null>
Path=Level1:Level2:1, key=1, value=<null>
Regression?
Yes. If you edit the project file to reference version 5.0.0 of Microsoft.Extensions.Configuration and Microsoft.Extensions.Configuration.Xml the output is
M.E.Configuration version 5.0.0.0
Path=Level1:Level2:Key1, key=Key1, value=Value1
Path=Level1:Level2:Key2, key=Key2, value=Value2
Known Workarounds
Don't use XML attributes at all. This works:
<configuration>
<Level1>
<Level2>
<Key1>Value1</Key1>
<Key2>Value2</Key2>
</Level2>
</Level1>
</configuration>
But it requires repeating each key, which is annoying when they're long.
A mix of the two does not work, however. This:
<configuration>
<Level1>
<Level2>
<Key1>Value1</Key1>
<Key2>Value2</Key2>
</Level2>
<Level2 Key3="Value3" />
</Level1>
</configuration>
still outputs
M.E.Configuration version 6.0.0.0
Path=Level1:Level2:0, key=0, value=<null>
Path=Level1:Level2:1, key=1, value=<null>
Configuration
.NET 6.0.5, Windows 7 x64
Other information
https://docs.microsoft.com/en-us/dotnet/core/extensions/configuration-providers#xml-configuration-provider mentions
In .NET 5 and earlier versions, add the name attribute to distinguish repeating elements that use the same element name. In .NET 6 and later versions, the XML configuration provider automatically indexes repeating elements. That means you don't have to specify the name attribute, except if you want the "0" index in the key and there's only one element.
I suspect this is related to that, but it's certainly not obvious from the docs that this would happen (the value would become null). If this is intentional it's a breaking change (not listed on https://docs.microsoft.com/en-us/dotnet/core/compatibility/6.0 as far as I can see), and a pretty bad one.
It works when there is only one item in the list:
<configuration>
<Level1>
<Level2 OnlyKey="OnlyValue" />
</Level1>
</configuration>
outputs
M.E.Configuration version 6.0.0.0
Path=Level1:Level2:OnlyKey, key=OnlyKey, value=OnlyValue
as expected.
Tagging subscribers to this area: @dotnet/area-extensions-configuration See info in area-owners.md if you want to be subscribed.
Issue Details
Description
I have a configuration file like this:
<configuration>
<Level1>
<Level2 Key1="Value1" />
<Level2 Key2="Value2" />
</Level1>
</configuration>
that I read using Microsoft.Extensions.Configuration.IConfigurationSection:
foreach (var configItem in configRoot.GetSection("Level1:Level2").GetChildren())
Console.WriteLine("Path={0}, key={1}, value={2}", configItem.Path, configItem.Key, configItem.Value ?? "<null>");
Up to v5.0.0 it returned the expected keys and values, but from v6.0.0 returns "0" and "1" for keys and nulls for values.
Reproduction Steps
Expected behavior
M.E.Configuration version 6.0.0.0 Path=Level1:Level2:Key1, key=Key1, value=Value1 Path=Level1:Level2:Key2, key=Key2, value=Value2
Actual behavior
M.E.Configuration version 6.0.0.0 Path=Level1:Level2:0, key=0, value=
Path=Level1:Level2:1, key=1, value=
Regression?
Yes. If you edit the project file to reference version 5.0.0 of Microsoft.Extensions.Configuration and Microsoft.Extensions.Configuration.Xml the output is
M.E.Configuration version 5.0.0.0 Path=Level1:Level2:Key1, key=Key1, value=Value1 Path=Level1:Level2:Key2, key=Key2, value=Value2
Known Workarounds
Don't use XML attributes at all. This works:
<configuration>
<Level1>
<Level2>
<Key1>Value1</Key1>
<Key2>Value2</Key2>
</Level2>
</Level1>
</configuration>
But it requires repeating each key, which is annoying when they're long.
A mix of the two does not work, however. This:
<configuration>
<Level1>
<Level2>
<Key1>Value1</Key1>
<Key2>Value2</Key2>
</Level2>
<Level2 Key3="Value3" />
</Level1>
</configuration>
still outputs
M.E.Configuration version 6.0.0.0 Path=Level1:Level2:0, key=0, value=
Path=Level1:Level2:1, key=1, value=
Configuration
.NET 6.0.5, Windows 7 x64
Other information
https://docs.microsoft.com/en-us/dotnet/core/extensions/configuration-providers#xml-configuration-provider mentions
In .NET 5 and earlier versions, add the name attribute to distinguish repeating elements that use the same element name. In .NET 6 and later versions, the XML configuration provider automatically indexes repeating elements. That means you don't have to specify the name attribute, except if you want the "0" index in the key and there's only one element.
I suspect this is related to that, but it's certainly not obvious from the docs that this would happen (the value would become null). If this is intentional it's a breaking change (not listed on https://docs.microsoft.com/en-us/dotnet/core/compatibility/6.0 as far as I can see), and a pretty bad one.
| Author: | loop-evgeny |
|---|---|
| Assignees: | - |
| Labels: |
|
| Milestone: | - |
Any chance of this being fixed in 6.0.x?
Since this is a regression from 5.0, we should backport this to 6.0.x.
Any estimate on when this might get fixed? We want to upgrade to .NET 6 and need to decide whether to work around it by transforming all XML attributes to elements or just wait for the fix.
This looks like a breaking change due to https://github.com/dotnet/runtime/pull/44608. The issue is that the repeated elements:
<Level1>
<Level2 Key1="Value1" />
<Level2 Key2="Value2" />
</Level1>
are not "named", and so now they are getting an "index" injected into them. If you look at the configItem in var configItem in configRoot.GetSection("Level1:Level2").GetChildren(), you'll see that its path is Level1:Level2:0 and not Level1:Level2:Key1 as it was before #44608. This is due to the 0 and 1 indices getting injected into the path. The full paths of the configuration:
| Before Item1 | Level1:Level2:Key1:Value1 |
| Before Item2 | Level1:Level2:Key2:Value2 |
| After Item1 | Level1:Level2:0:Key1:Value1 |
| After Item2 | Level1:Level2:1:Key2:Value2 |
This was a breaking change that should have been documented in 6.0, but it doesn't appear that was the case. From the PR description:
When siblings are detected, automatically append an index to the generated configuration keys. This makes it work exactly the same as the JSON configuration provider with JSON arrays.
cc @amoerie - in case there is anything I am missing here.
I believe this is "by design" and we just need to add a breaking change documentation for it. See https://github.com/dotnet/runtime/pull/44608 for more information on why this was done.
It looks like this is a use case that I was not aware of, and that was not present in the unit tests. (Because I distinctly remember not removing any in my pull request)
I think I agree with @eerhardt that this is by design, albeit accidentally. The fact that this worked under .NET 5 also seemed accidental, since it was not covered by any test.
My 2 cents on this issue: I would investigate the effort to simply update the XML files. If that is too much trouble, nothing stops you from using a custom ConfigurationProvider, which is what I did before .NET 6 was released.
Thinking about it more:
<Level1>
<Level2 Key1="Value1" />
<Level2 Key2="Value2" />
</Level1>
Is kind of odd before #44608. You need to ensure you use different attribute values in the Level2 elements. If both elements had the same attribute, you'd get an exception:
<Level1>
<Level2 Key1="Value1" />
<Level2 Key1="Value2" Key2="Value2" />
</Level1>
You can update your XML to collapse the 2 attributes into the same element, and get the original behavior:
<Level1>
<Level2 Key1="Value1" Key2="Value2" />
</Level1>
@tarekgh @maryamariyan - do you think this deserves a breaking change doc? Or is this enough of an edge case that it doesn't justify documenting this change separately from this issue?
It will be good if we file a breaking change issue to have it documented and can point users to such case. I marked https://github.com/dotnet/runtime/pull/44608 with the breaking change label.
I have created https://github.com/dotnet/docs/issues/30660 to document this breaking change.
I'm going to close this issue since this is now by design behavior.
You can update your XML to collapse the 2 attributes into the same element, and get the original behavior:
<Level1>
<Level2 Key1="Value1" Key2="Value2" />
</Level1>
Thanks, this works as a workaround. It's still a bit of a gotcha, of course, and definitely needs to be documented.
I see that https://github.com/dotnet/runtime/pull/44608 was prompted by Serilog and it's actually with Serilog overrides that I ran into this issue, too.