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