You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ValidateOnStart()与[Required]在.NET控制台应用中未生效的问题

问题背景

appsettings.json 内容

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

关联的配置类

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; }
}

注:故意移除了appsettings.json中的Age字段,模拟配置无效场景。

控制台应用代码

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; }
}

当启用以下代码块时,会抛出异常:

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

使用的NuGet包

<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" />

疑问

  1. 为什么ValidateOnStart()没有在解析SettingsOptions之前抛出异常?
  2. 为什么[Required]特性未生效(缺少Age字段时未抛异常,而是设为默认值0)?

解答

问题1:ValidateOnStart()未触发启动时验证的原因

ValidateOnStart()的验证逻辑依赖于应用主机(Host)的启动流程:它会注册一个StartupValidationHostedService,这个服务仅在主机启动时自动执行配置验证。但你的代码直接使用ServiceCollection构建服务提供者,没有构建并启动IHost实例,因此启动时验证的逻辑不会被触发。

如果要让ValidateOnStart()生效,需要改用Host.CreateDefaultBuilder搭建应用主机,示例代码如下:

var host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((context, services) =>
    {
        services.AddOptions<SettingsOptions>()
            .BindConfiguration(SettingsOptions.SECTION)
            .ValidateDataAnnotations()
            .ValidateOnStart();
    })
    .Build();

// 启动主机时会自动触发验证
await host.StartAsync();

Console.WriteLine("Done");
await host.WaitForShutdownAsync();

问题2:[Required]对值类型无效的原因

int是值类型,默认值为0。[Required]特性的验证规则是仅当属性值为null时判定为缺失。对于值类型来说,即使配置中没有对应字段,属性会被初始化为默认值0而非null,因此[Required]不会判定为缺失,自然不会抛出异常。

要解决这个问题,需将Age改为可空值类型int?,这样当配置中没有Age字段时,属性值会是null,触发[Required]的验证逻辑:

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; }
}

内容的提问来源于stack exchange,提问作者user23207089

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 02:28:14