Skip to content

ConfigurationBinder silently coerces null into default values for non-nullable typed properties (JSON configuration provider) #129478

@Justin-Nietzel

Description

@Justin-Nietzel

Description

Related: dotnet/docs#46890 [Breaking change]: Preserving Null Values in Configuration

In versions before 10.0, Microsoft.Extensions.Configuration would throw an Exception when attempting to parse a null value into a non-nullable value type.

// Exception message from Microsoft.Extensions.Configuration 9.0.17
Failed to convert configuration value at 'TestOption:TheDayOfTheWeek' to type 'System.DayOfWeek'.

Starting in version 10.0, null will be silently coerced into the default value for that property.

Reproduction Steps

Using the default settings for a file-based app will produce different behavior for versions of Microsoft.Extensions.Hosting before 10.0 because PublishAot is enabled by default.

This might a completely separate bug, but the behavior of Microsoft.Extensions.Configuration@9.0.17 depends on where AOT is enable or not. When enabled, it behaves like Microsoft.Extensions.Configuration@10.09. I'm not familiar with AOT, so I'm not sure if minor differences in behavior are acceptable or not.

// #:package Microsoft.Extensions.Hosting@9.0.17
#:package Microsoft.Extensions.Hosting@10.0.9
#:property TargetFramework=net10.0
#:property PublishAot=false

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System.Text;

string jsonString1 =
$$"""
{
  "TestOption": {
    "TheDayOfTheWeek": null
  }
}
""";

TestJson(jsonString1);

static void TestJson(string jsonString)
{
    var builder = Host.CreateEmptyApplicationBuilder(null);
    builder.Configuration
        .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(jsonString)));
    builder.Services
        .AddOptions<TestOption>()
        .BindConfiguration("TestOption");
    var host = builder.Build();

    try
    {
        var options = host.Services.GetRequiredService<IOptions<TestOption>>().Value;
        Console.WriteLine(options.TheDayOfTheWeek);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

public record TestOption
{
    public required DayOfWeek TheDayOfTheWeek { get; init; }
}

Expected behavior

InvalidOperationException with the message

Failed to convert configuration value at 'TestOption:TheDayOfTheWeek' to type 'System.DayOfWeek'.

Actual behavior

No InvalidOperationException is thrown and its message is written to the console.

Regression?

Yes, this was changed with .NET preview 7 according to dotnet/docs#46890.

Known Workarounds

No response

Configuration

Windows 11 Pro 25H2 (x64)
Dotnet 10.0.202
AOT Disabled (this makes a difference)

Other information

I personally prefer being as explicit as possible and differentiating between null and 0, but I don't think I can come up with a realistic use case where the distinction matters. The lack of any mention of non-nullable value types in dotnet/docs#46890 leads me to believe these changes were unintentional. It may be more appropriate to just update the documentation.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions