Search code examples
c#data-annotationsioptions

ValidateOnStart() and [Required] don't seem to work


I have an appsettings.json with the following contents:

{
    "Settings": {
        "Greeting":  "Hello World"
    }
}

that I associate with

public sealed class SettingsOptions
{
    public const string SECTION = "Settings";

    [Required(ErrorMessage = "{0} is required.")]
    public string Greeting { get; init; } = default!;

    [Required(ErrorMessage = "{0} is required.")]
    [Range(1, 10, ErrorMessage = "{0} must be between {1} and {2} inclusive.")]
    public int Age { get; init; }
}

Note: I intentionally removed Age in appsettings.json to simulate an invalid case.

I expect my console app below throws an exception but apparently it does not when the app starts.

var configuration = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json")
        .Build();

IServiceCollection services = new ServiceCollection();

services.AddSingleton<IConfiguration>(configuration);
services.AddOptions<SettingsOptions>()
        .BindConfiguration(SettingsOptions.SECTION)
        .ValidateDataAnnotations()
        .ValidateOnStart();


var provider = services.BuildServiceProvider();

#if false // only for simulation
var settings = provider.GetRequiredService<IOptions<SettingsOptions>>().Value;
Console.WriteLine($"greeting: {settings.Greeting}, age: {settings.Age}.");
#endif

Console.WriteLine("Done");
Console.ReadKey();

public sealed class SettingsOptions
{
    public const string SECTION = "Settings";

    [Required(ErrorMessage = "{0} is required.")]
    public string Greeting { get; init; } = default!;

    [Required(ErrorMessage = "{0} is required.")]
    [Range(1, 10, ErrorMessage = "{0} must be between {1} and {2} inclusive.")]
    public int Age { get; init; }
}

It does throw an exception if I enable

#if true// only for simulation
var settings = provider.GetRequiredService<IOptions<SettingsOptions>>().Value;
Console.WriteLine($"greeting: {settings.Greeting}, age: {settings.Age}.");
#endif

I have two questions:

  • Why doesn't ValidateOnStart() throw an exception before other parts need to resolve SettingsOptions?
  • Why doesn't [Required] have any effect (it does not throw an exception when Age in appsettings.json is missing but instead it is set to its default value 0)?

Edit:

<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" Version="8.0.0" />

Solution

  • It won't work inside the console app unless you implement the generic host in the console app. ValidateOnStart method is adding ValidationHostedService which is IHostedService which is triggered on host.Run().