如何在 ASP.NET Core 2.2 中使用 IValidateOptions 验证配置设置?

Dav*_*lli 15 c# asp.net-core asp.net-core-2.2

Microsoft 的 ASP.NET Core 文档简要提到您可以IValidateOptions<TOptions>从 appsettings.json实施以验证配置设置,但未提供完整示例。IValidateOptions打算如何使用?进一步来说:

  • 你在哪里连接你的验证器类?
  • 如果验证失败,您如何记录有用的消息来解释问题所在?

我实际上已经找到了解决方案。我正在发布我的代码,因为我目前IValidateOptions在 Stack Overflow 上找不到任何提及。

Dav*_*lli 22

我最终在添加了选项验证功能提交中找到了如何完成此操作的示例。与 asp.net core 中的许多东西一样,答案是将您的验证器添加到 DI 容器中,它将自动被使用。

使用这种方法,PolygonConfiguration验证后进入 DI 容器,并可以注入需要它的控制器。我更喜欢这个而不是注入IOptions<PolygonConfiguration>我的控制器。

似乎验证代码在第一次PolygonConfiguration从容器请求实例时运行(即当控制器被实例化时)。在启动期间尽早验证可能会很好,但我现在对此感到满意。

这是我最终做的:

public class Startup
{
    public Startup(IConfiguration configuration, ILoggerFactory loggerFactory)
    {
        Configuration = configuration;
        Logger = loggerFactory.CreateLogger<Startup>();
    }

    public IConfiguration Configuration { get; }
    private ILogger<Startup> Logger { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

        //Bind configuration settings
        services.Configure<PolygonConfiguration>(Configuration.GetSection(nameof(PolygonConfiguration)));

        //Add validator
        services.AddSingleton<IValidateOptions<PolygonConfiguration>, PolygonConfigurationValidator>();

        //Validate configuration and add to DI container
        services.AddSingleton<PolygonConfiguration>(container =>
        {
            try
            {
                return container.GetService<IOptions<PolygonConfiguration>>().Value;
            }
            catch (OptionsValidationException ex)
            {
                foreach (var validationFailure in ex.Failures)
                    Logger.LogError($"appSettings section '{nameof(PolygonConfiguration)}' failed validation. Reason: {validationFailure}");

                throw;
            }
        });
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
       ...
    }
}

Run Code Online (Sandbox Code Playgroud)

带有一些有效和无效值的 appSettings.json

{
  "PolygonConfiguration": {
    "SupportedPolygons": [
      {
        "Description": "Triangle",
        "NumberOfSides": 3
      },
      {
        "Description": "Invalid",
        "NumberOfSides": -1
      },
      {
        "Description": "",
        "NumberOfSides": 6
      }
    ]
  }
}
Run Code Online (Sandbox Code Playgroud)

验证器类本身

    public class PolygonConfigurationValidator : IValidateOptions<PolygonConfiguration>
    {
        public ValidateOptionsResult Validate(string name, PolygonConfiguration options)
        {
            if (options is null)
                return ValidateOptionsResult.Fail("Configuration object is null.");

            if (options.SupportedPolygons is null || options.SupportedPolygons.Count == 0)
                return ValidateOptionsResult.Fail($"{nameof(PolygonConfiguration.SupportedPolygons)} collection must contain at least one element.");

            foreach (var polygon in options.SupportedPolygons)
            {
                if (string.IsNullOrWhiteSpace(polygon.Description))
                    return ValidateOptionsResult.Fail($"Property '{nameof(Polygon.Description)}' cannot be blank.");

                if (polygon.NumberOfSides < 3)
                    return ValidateOptionsResult.Fail($"Property '{nameof(Polygon.NumberOfSides)}' must be at least 3.");
            }

            return ValidateOptionsResult.Success;
        }
    }
Run Code Online (Sandbox Code Playgroud)

和配置模型

    public class Polygon
    {
        public string Description { get; set; }
        public int NumberOfSides { get; set; }
    }

    public class PolygonConfiguration
    {
        public List<Polygon> SupportedPolygons { get; set; }
    }
Run Code Online (Sandbox Code Playgroud)


Jul*_*ian 21

未来版本正在考虑进行急切验证(启动时快速失败)。

从 .NET 6 开始,这可以通过ValidateOnStart()

用法:

services.AddOptions<ComplexOptions>()
  .Configure(o => o.Boolean = false)
  .Validate(o => o.Boolean, "Boolean must be true.")
  .ValidateOnStart();
Run Code Online (Sandbox Code Playgroud)

背景信息: 拉取请求:添加 Eager Options 验证:ValidateOnStart API


090*_*9EM 5

现在可能为时已晚,但为了其他偶然发现此问题的人的利益......

在文档部分的底部附近(在问题中链接到),出现这一行

正在考虑在未来版本中进行快速验证(启动时快速失败)。

在搜索更多关于此的信息时,我遇到了这个 github 问题,它提供了一个 IStartupFilter 和一个 IOptions 的扩展方法(我在下面重复了,以防问题消失)...

此解决方案可确保在应用程序“运行”之前验证选项。

public static class EagerValidationExtensions {
    public static OptionsBuilder<TOptions> ValidateEagerly<TOptions>(this OptionsBuilder<TOptions> optionsBuilder)
        where TOptions : class, new()
    {
        optionsBuilder.Services.AddTransient<IStartupFilter, StartupOptionsValidation<TOptions>>();
        return optionsBuilder;
    }
}

public class StartupOptionsValidation<T>: IStartupFilter
{
    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return builder =>
        {
            var options = builder.ApplicationServices.GetRequiredService(typeof(IOptions<>).MakeGenericType(typeof(T)));
            if (options != null)
            {
                var optionsValue = ((IOptions<object>)options).Value;
            }

            next(builder);
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我有一个从 ConfigureServices 中调用的扩展方法,看起来像这样

services
  .AddOptions<SomeOptions>()
  .Configure(options=>{ options.SomeProperty = "abcd" })
  .Validate(x=>
  {
      // do FluentValidation here
  })
  .ValidateEagerly();
Run Code Online (Sandbox Code Playgroud)


iro*_*ght 5

只需构建一个用于将FluentValidation与 Microsoft.Extensions.Options集成的

https://github.com/iron9light/FluentValidation.Extensions

nuget 在这里:https ://www.nuget.org/packages/IL.FluentValidation.Extensions.Options/

样本:

public class MyOptionsValidator : AbstractValidator<MyOptions> {
    // ...
}

using IL.FluentValidation.Extensions.Options;

// Registration
services.AddOptions<MyOptions>("optionalOptionsName")
    .Configure(o => { })
    .Validate<MyOptions, MyOptionsValidator>(); // ? Register validator type

// Consumption
var monitor = services.BuildServiceProvider()
    .GetService<IOptionsMonitor<MyOptions>>();

try
{
    var options = monitor.Get("optionalOptionsName");
}
catch (OptionsValidationException ex)
{
}
Run Code Online (Sandbox Code Playgroud)