Repository navigation
.NET Core options binding with JSON files: inconsistent behavior with invalid collection items #36129
Description
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.Jsonversion 2.2.0Microsoft.Extensions.DependencyInjectionversion 2.2.0Microsoft.Extensions.Options.ConfigurationExtensionsversion 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
Kahbazi commentedon Sep 16, 2019
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?
peter-perot commentedon Sep 16, 2019
IMHO the actual problem here is that elements in which even one single property cannot be bound (in my example it's the
Categoryproperty) 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.
Kahbazi commentedon Sep 16, 2019
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?
peter-perot commentedon May 8, 2020
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.jsonin 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.
maryamariyan commentedon Oct 6, 2020
closing as dupe of #36015