.NET 7中如何用FluentValidation在启动和变更时验证Options
.NET 7 Web API中使用FluentValidation实现Options启动时验证
问题描述
我正在使用.NET 7创建Web API项目,希望通过FluentValidation配置并验证Options。现有LoggingOptions和对应的验证器代码如下:
public sealed class LoggingOptions { public const string SectionName = "Logging"; public LogLevel LogLevel { get; init; } = LogLevel.Information; } public sealed class LoggingOptionsValidator : AbstractValidator<LoggingOptions> { public LoggingOptionsValidator() { RuleFor(options => options.LogLevel).IsInEnum(); } }
当前Program.cs代码能在Options变更时验证,但启动时不会验证配置:
var builder = WebApplication.CreateBuilder(args); builder.Services.Configure<LoggingOptions>(builder.Configuration.GetSection(LoggingOptions.SectionName)); builder.Services.AddTransient<IValidator<LoggingOptions>, LoggingOptionsValidator>(); builder.Services.AddControllers(); var app = builder.Build(); app.Services.GetRequiredService<IOptionsMonitor<LoggingOptions>>().OnChange(options => { var validator = app.Services.GetRequiredService<IValidator<LoggingOptions>>(); var validationResult = validator.Validate(options); if (!validationResult.IsValid) { var validationErrors = validationResult.Errors.Select(error => error.ErrorMessage); throw new OptionsValidationException(nameof(LoggingOptions), typeof(LoggingOptions), validationErrors); } }); app.UseHttpsRedirection(); app.MapControllers(); app.Run();
尝试用ValidateOnStart()实现启动时验证,但不知道如何在验证逻辑中获取IValidator<LoggingOptions>实例:
builder .Services .AddOptions<LoggingOptions>() .Validate(options => { var validator = ???; // 如何获取验证器? var validationResult = validator.Validate(options); if (!validationResult.IsValid) { var validationErrors = validationResult.Errors.Select(error => error.ErrorMessage); throw new OptionsValidationException(nameof(LoggingOptions), typeof(LoggingOptions), validationErrors); } return true; }) .ValidateOnStart() .PostConfigure(options => { var validator = ???; // 如何获取验证器? var validationResult = validator.Validate(options); if (!validationResult.IsValid) { var validationErrors = validationResult.Errors.Select(error => error.ErrorMessage); throw new OptionsValidationException(nameof(LoggingOptions), typeof(LoggingOptions), validationErrors); } });
请问如何正确实现Options在启动时的验证?
解决方案
方法1:使用FluentValidation扩展方法(推荐)
FluentValidation提供了针对Options的原生扩展,无需手动解析验证器,直接整合到配置流程中。
首先安装依赖包:
dotnet add package FluentValidation.DependencyInjectionExtensions
修改Program.cs,整合启动时验证与变更时验证:
var builder = WebApplication.CreateBuilder(args); // 自动注册程序集中的所有验证器 builder.Services.AddValidatorsFromAssemblyContaining<LoggingOptionsValidator>(); // 配置Options并启用双阶段验证 builder.Services.AddOptions<LoggingOptions>() .Bind(builder.Configuration.GetSection(LoggingOptions.SectionName)) // 注入验证器进行验证,失败抛出标准OptionsValidationException .Validate<IValidator<LoggingOptions>>((options, validator) => { var result = validator.Validate(options); if (!result.IsValid) { throw new OptionsValidationException( nameof(LoggingOptions), typeof(LoggingOptions), result.Errors.Select(e => e.ErrorMessage) ); } return true; }) .ValidateOnStart(); // 启用启动时验证 builder.Services.AddControllers(); var app = builder.Build(); // 保留配置变更时的验证逻辑 app.Services.GetRequiredService<IOptionsMonitor<LoggingOptions>>().OnChange(options => { var validator = app.Services.GetRequiredService<IValidator<LoggingOptions>>(); var validationResult = validator.Validate(options); if (!validationResult.IsValid) { var validationErrors = validationResult.Errors.Select(error => error.ErrorMessage); throw new OptionsValidationException(nameof(LoggingOptions), typeof(LoggingOptions), validationErrors); } }); app.UseHttpsRedirection(); app.MapControllers(); app.Run();
方法2:手动通过服务提供者解析验证器
如果不想用扩展方法,可以利用PostConfigure的服务注入能力,直接获取验证器:
var builder = WebApplication.CreateBuilder(args); builder.Services.Configure<LoggingOptions>(builder.Configuration.GetSection(LoggingOptions.SectionName)); builder.Services.AddTransient<IValidator<LoggingOptions>, LoggingOptionsValidator>(); // 配置启动时验证 builder.Services.AddOptions<LoggingOptions>() .ValidateOnStart() // PostConfigure支持注入IServiceProvider .PostConfigure((options, serviceProvider) => { var validator = serviceProvider.GetRequiredService<IValidator<LoggingOptions>>(); var result = validator.Validate(options); if (!result.IsValid) { throw new OptionsValidationException( nameof(LoggingOptions), typeof(LoggingOptions), result.Errors.Select(e => e.ErrorMessage) ); } }); builder.Services.AddControllers(); var app = builder.Build(); // 变更时验证逻辑不变 app.Services.GetRequiredService<IOptionsMonitor<LoggingOptions>>().OnChange(options => { var validator = app.Services.GetRequiredService<IValidator<LoggingOptions>>(); var validationResult = validator.Validate(options); if (!validationResult.IsValid) { var validationErrors = validationResult.Errors.Select(error => error.ErrorMessage); throw new OptionsValidationException(nameof(LoggingOptions), typeof(LoggingOptions), validationErrors); } }); app.UseHttpsRedirection(); app.MapControllers(); app.Run();
核心要点
Validate<IValidator<T>>是最简洁的方式,直接将验证器注入到验证逻辑中,避免手动解析服务。ValidateOnStart()会在应用启动阶段立即验证配置,验证失败直接终止启动,防止应用带着无效配置运行。- 启动时验证与变更时验证是独立的,
ValidateOnStart()不覆盖配置变更后的验证场景,仍需通过IOptionsMonitor.OnChange监听处理。
内容的提问来源于stack exchange,提问作者baitendbidz
相关产品推荐
相关产品推荐

