Home > other >  Null property when binding to empty array in appsettings.json
Null property when binding to empty array in appsettings.json

Time:10-13

I stumbled across an issue when using the Options pattern where model properties are being set to null when bound to an empty JSON array.

appsettings.json:

{
  "MyOptions": {
    "Values": []
  }
}

MyOptions.cs:

public sealed class MyOptions
{
  public IEnumerable<string> Values { get; set; }
}

Startup.cs:

...
services.Configure<MyOptions>(                            
  _configuration.GetSection(nameof(MyOptions));
...

The above configuration successfully builds and injects an IOptions<MyOptions> when required, however Values property is set to null rather than an empty enumerable. This would cause the following code to throw a NullReferenceException:

public class MyService
{
  public MyService(IOptions<MyOptions> options)
  {
    var values = options.Value.Values;

    foreach (var val in values)
    {
      // Do something
    }
  }
}

This has been raised as an issue on the dotnet repo (https://github.com/dotnet/extensions/issues/1341) although MS seem to have closed it as "working as designed".

Is there a workaround to prevent the NullReferenceException being thrown?

CodePudding user response:

One possible workaround is to alter the MyOptions class to use backing fields instead of auto-properties and in the getter a null-coalescing operator to return an empty IEnumerable:

public sealed class MyOptions
{
  private IEnumerable<string> _values;

  public IEnumerable<string> Values
  {
    get => _values ?? new List<string>();
    set => _values = value;
  }
}

CodePudding user response:

I always make sure my properties inside a configuration class have meaningful default values assigned:

public sealed class MyOptions
{
  public IEnumerable<string> Values { get; set; } = Array.Empty<string>();
}

This way I don't have to check for null or not configured values each time I'm using my configuration object.

CodePudding user response:

I think that MS gave a right answer "working as designed". You have allways remember Murphy's Law - anything that can go wrong will go wrong. To create the robust code anyone should expect null value for any nullable property, doesn't matter how it was init. It can always became null somewhere on the way. So I am always checking for null

  if (options.Value.Values != null)
    foreach (var val in options.Value.Values)
    {
       // Do something
    } else ... return error;
       

I don' t know how myoptions are important for this application, but I usually check appdata data in startup already

var myOptions = Configuration.GetSection(nameof(MyOptions));
if (myOptions.Exists())
{
    services.Configure<MyOptions>(myOptions);
    services.AddScoped(typeof(MyService));
} else ... return error
  • Related