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" />
疑问
- 为什么
ValidateOnStart()没有在解析SettingsOptions之前抛出异常? - 为什么
[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
相关产品推荐
相关产品推荐

