Skip to content

.NET Core options binding with JSON files: inconsistent behavior with invalid collection items #36129

Closed

Description

@peter-perot

Describe the bug

I have detected an inconsistent and annoying behavior with .NET Core options binding when JSON files, arrays and lists come into play. In a nutshell: options that contain invalid data and are items of an array or list, are simply dropped or nulled out.

To Reproduce

Let's use the following JSON file:

{
  "Category": "Bravo",
  "SubOptionsAsArray": [
    {
      "Category": "Bravo"
    },
    {
      "Category": "Zulu"
    }
  ],
  "SubOptionsAsList": [
    {
      "Category": "Bravo"
    },
    {
      "Category": "Zulu"
    }
  ]
}

I'm using a property named Category on different levels: on top-level, as and array item, and as a list item. This property is an enum can take the values Alpha and Bravo. Value Zulu is considered invalid.

Here is the C# code. Add the JSON file to the project and name it example.json.

Used packages:

  • Microsoft.Extensions.Configuration.Json version 2.2.0
  • Microsoft.Extensions.DependencyInjection version 2.2.0
  • Microsoft.Extensions.Options.ConfigurationExtensions version 2.2.0
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Example
{
  class Program
  {
    static void Main(string[] args)
    {
      var configuration = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("example.json")
        .Build();

      var serviceProvider = new ServiceCollection()
        .Configure<MyOptions>(configuration)
        .BuildServiceProvider();

      var options = serviceProvider.GetRequiredService<IOptions<MyOptions>>().Value;
    }
  }

  public class MyOptions
  {
    public MySubOptions[] SubOptionsAsArray { get; set; }

    public List<MySubOptions> SubOptionsAsList { get; set; }

    public MyCategory Category { get; set; }
  }

  public class MySubOptions
  {
    public MyCategory Category { get; set; }
  }

  public enum MyCategory
  {
    Alpha,
    Bravo
  }
}

Set a breakpoint after the line where the options variable is set und run the program. Now let's examine the options variable:

options.SubOptionsAsArray.Length is 2. The first element is the correctly bound element with category Bravo, but the second element is null.

options.SubOptionsAsList.Count is 1. The first element is the correctly bound element with category Bravo. The second element with invalid category Zulu has simply been dropped.

IAW: Throwing the options back into a JSON file would look like this:

{
  "Category": "Bravo",
  "SubOptionsAsArray": [
    {
      "Category": "Bravo"
    },
    null
  ],
  "SubOptionsAsList": [
    {
      "Category": "Bravo"
    }
  ]
}

This is really annoying since I don't get any information on the invalid data.

Expected behavior

When I write JSON like this...

{
  "Category": "Zulu"
}

... and run the program, the framework throws an exception indicating that Category value Zulu cannot be bound. This is the correct behavior I would expect to be the same with nested data like array or list items, but not silently dropping invalid data or replacing it with null.

The problem is that a user who has mistyped something that is part of the deeper levels of the JSON file, will never get a hint that something went wrong. Instead the invalid part of the JSON file is dropped or nulled out.

Activity

  1. Kahbazi commented on Sep 16, 2019

    @Kahbazi
    Member

    as @peter-perot pointed out there are two different behavior on handling exception on array binding and list binding.

    In array binding first an array with the length of config items are declared and if an exception occurs that index of array remain null. On the other hand on list binding there is only the list declaration and Add method is being called on successful binds.

    Is this by design? Should array binding works like list binding?

  2. peter-perot commented on Sep 16, 2019

    @peter-perot
    Author

    IMHO the actual problem here is that elements in which even one single property cannot be bound (in my example it's the Category property) are removed from arrays and lists without throwing any exceptions. In this way, it is pretended that the binding is working correctly, although it is not.

    The application is not capable to give a clue to the user about the mistake he made; this way the user is thinking that his configuration is flawless.

  3. Kahbazi commented on Sep 16, 2019

    @Kahbazi
    Member

    I dig deeper and it seems an exception is thrown for when type is Dictionary, so is there any reason that it's different for list and array?

  4. peter-perot commented on May 8, 2020

    @peter-perot
    Author

    I think this issue should be reconsidered since it can lead to hard to find bugs in deployment scenarios.

    Image you configure some of your appsettings.json in your deployment system (e.g. using placeholder variables in Octopus Deploy), and if there is some invalid value, this mistake is silently swallowed by the deployed application.

    Furthermore this behavior makes it even harder for the deployer to find out what is configured wrong.

  5. transferred this issue fromdotnet/extensionson May 8, 2020
  6. added this to the Future milestone on May 8, 2020
  7. removed
    untriagedNew issue has not been triaged by the area owner
    on Jul 1, 2020
  8. maryamariyan commented on Oct 6, 2020

    @maryamariyan
    Contributor

    closing as dupe of #36015

  9. ghost locked as resolved and limited conversation to collaborators on Dec 12, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions