Skip to content

Mixing JSON and XML configuration leads to subtle bugs when using the "Name" attribute #36541

Closed

Description

When configuration is loaded from both a JSON and an XML file, the configuration may be invalid depending on the order in which the files are added to the ConfigurationBuilder

Functional impact

In our test scenario, Serilog configuration failed to load properly because of this bug.

Minimal repro steps

  1. Create a .NET Core 2.0 console application with an appsettings.xml and an appsettings.json file
  2. Fill in the appsettings.xml file like this:
<settings>
  <Serilog>
    <WriteTo Name="0">
      <Args>
        <pathFormat>C:\FooBar\Logs</pathFormat>
      </Args>
    </WriteTo>
  </Serilog>
</settings>
  1. Fill in the appsettings.json like this:
{
  "Serilog": {
    "WriteTo": [
      {
        "Name": "RollingFile"
      }
    ]
  }
}
  1. Configure both files with "Build Action" set to "Content"
  2. Update Program.cs to the following:
  class Program
  {
    static void Main(string[] args)
    {
      var jsonConfiguration = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json", optional: false)
        .Build();
      var xmlConfiguration = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddXmlFile("appsettings.xml", optional: false)
        .Build();
      
      Console.WriteLine("*** JSON CONFIG ***");
      foreach (var (key, value) in jsonConfiguration.AsEnumerable())
      {
        Console.WriteLine($"{key, -25} = {value}");
      }
      Console.WriteLine();

      Console.WriteLine("*** XML CONFIG ***");
      foreach (var (key, value) in xmlConfiguration.AsEnumerable())
      {
        Console.WriteLine($"{key, -25} = {value}");
      }
      Console.WriteLine();

      Console.ReadLine();
    }
  }
  1. Add the following NuGet references to your project
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Xml" Version="2.0.1" />
  </ItemGroup>
  1. Startup the application

Expected result

The XML config should NOT produce a 'Name' key in the configuration

I would expect the following output:

*** JSON CONFIG ***
Serilog                   =
Serilog:WriteTo           =
Serilog:WriteTo:0         =
Serilog:WriteTo:0:Name    = RollingFile

*** XML CONFIG ***
Serilog                   =
Serilog:WriteTo           =
Serilog:WriteTo:0         =
Serilog:WriteTo:0:Args    =
Serilog:WriteTo:0:Args:pathFormat = C:\FooBar\Logs

Actual result

The XML config produces a 'Name' key in the configuration

*** JSON CONFIG ***
Serilog                   =
Serilog:WriteTo           =
Serilog:WriteTo:0         =
Serilog:WriteTo:0:Name    = RollingFile

*** XML CONFIG ***
Serilog                   =
Serilog:WriteTo           =
Serilog:WriteTo:0         =
Serilog:WriteTo:0:Name    = 0
Serilog:WriteTo:0:Args    =
Serilog:WriteTo:0:Args:pathFormat = C:\FooBar\Logs

Impact

As you can see, both the JSON and the XML file produce a key 'Serilog:WriteTo:0:Name', once with 0 and once with RollingFile. Due to the way the ConfigurationBuilder works, the last key will win.
When the value of Serilog:WriteTo:0:Name is 'RollingFile', Serilog will successfully configure the sink and configuration will succeed. When the value is '0' however, Serilog will skip configuring this sink.

This means that when the configuration builder is called like follows:

      var configuration = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json", optional: false)
        .AddXmlFile("appsettings.xml", optional: false)
        .Build();

It will not work, because the key Serilog:WriteTo:0:Name from the XML file wins, breaking Serilog.

If you change it to this however:

      var configuration = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddXmlFile("appsettings.xml", optional: false)
        .AddJsonFile("appsettings.json", optional: false)
        .Build();

The program will correctly configure Serilog.

Further information

This issue is actually closely related to another issue I've created here: dotnet/extensions#745
The proposed solution there also solves this issue, as it removes the need to use "Name" attributes to simulate arrays in XML and thus avoids this subtle problem.

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

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions