【问题标题】:How to trigger model validation inside IValidateOptions<T> implementation?如何在 IValidateOptions<T> 实现中触发模型验证?
【发布时间】:2021-08-05 05:30:29
【问题描述】:

我有一个 .Net 5 应用程序并想为我的配置添加验证器。鉴于此示例选项

public sealed class DatabaseOptions
{
    public string ConnectionString { get; set; }
}

我目前用这个实现来验证它

public sealed class DatabaseOptionsValidator : IValidateOptions<DatabaseOptions>
{
    public ValidateOptionsResult Validate(string name, DatabaseOptions databaseOptions)
    {
        List<string> validationFailures = new List<string>();

        if (string.IsNullOrEmpty(databaseOptions.ConnectionString))
            validationFailures.Add($"{nameof(databaseOptions.ConnectionString)} is required.");

        // ...

        if (validationFailures.Any())
        {
            return ValidateOptionsResult.Fail(validationFailures);
        }

        return ValidateOptionsResult.Success;
    }
}

我想避免实现自己的验证检查和错误消息,因为我知道数据注释已经完成了工作。

我将选项模型修改为这个

public sealed class DatabaseOptions
{
    [Required]
    [MinLength(9999999)] // for testing purposes
    public string ConnectionString { get; set; }
}

并希望找到一种方法来触发模型验证

public sealed class DatabaseOptionsValidator : IValidateOptions<DatabaseOptions>
{
    public ValidateOptionsResult Validate(string name, DatabaseOptions databaseOptions)
    {
        List<string> validationFailures = new List<string>();

        // trigger the model validation and add every error to the validationFailures list

        if (validationFailures.Any())
        {
            return ValidateOptionsResult.Fail(validationFailures);
        }

        return ValidateOptionsResult.Success;
    }
}

但不幸的是,我无法这样做。调试器点击了验证器,但我如何在 Validate 方法中触发验证?

【问题讨论】:

  • 你不能只通过ValidateDataAnnotations 使用DataAnnotations 验证器吗?见这里stackoverflow.com/q/55025197/491907。不是完全重复,但问题本身显示了一个示例,答案显示了数据注释验证器实际上是如何在内部工作的,因此您可以在必要时复制该实现。这实际上是相当基本的

标签: c# asp.net-web-api .net-core asp.net-core-webapi .net-5


【解决方案1】:

请查看 cmets,因为我的解决方案已经可用!


基于Rodrigo Rodrigues answer,我根据数据注释创建了自己的选项验证器

public sealed class OptionsValidator<TOptions> : IValidateOptions<TOptions> where TOptions : class
{
    public ValidateOptionsResult Validate(string name, TOptions options)
    {
        ValidationContext validationContext = new ValidationContext(options);
        List<ValidationResult> validationResults = new List<ValidationResult>();
        bool noValidationErrorsOccured = Validator.TryValidateObject(options, validationContext, validationResults, true);
        
        if (noValidationErrorsOccured) {
            return ValidateOptionsResult.Success;
        }
        
        IEnumerable<string> validationFailures = validationResults.Select(validationResult => validationResult.ErrorMessage);
        
        return ValidateOptionsResult.Fail(validationFailures);
    }
}

所以每当我想向我的 DI 容器添加验证器时,我都可以使用这个扩展方法

public static IServiceCollection AddOptionsValidator<TOptions>(this IServiceCollection serviceCollection) where TOptions : class
    => serviceCollection.AddSingleton<IValidateOptions<TOptions>, OptionsValidator<TOptions>>();

【讨论】:

  • 请注意,这实际上是 ValidateDataAnnotations 所做的。为什么要重新发明轮子?
  • @pinkfloydx33 抱歉,我认为这是自定义实现。我将如何在我的验证器中使用它?
  • 我有一个 .Net 5 应用程序,但找不到类似的东西,我必须在哪里调用它?我在 DI 服务注册中寻找它,但也作为静态助手的静态方法
  • 您需要 DataAnnotations 包并在 OptionsBuilder 上调用它。我在您的 OP 上链接的答案的链接中有一些示例。但是,如果您查看官方文档,大约在标题“选项验证”的一半时,它们会准确显示如何使用 DataAnnotations 包。 docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/…。您不需要自己的验证器,只需使用他们的验证器即可。
  • 啊,很酷。所以一切都保持不变,但不是打电话给serviceCollection.AddSingleton&lt;IValidateOptions&lt;MyOptions&gt;, OptionsValidator&lt;MyOptions&gt;&gt;(),我可以打电话给serviceCollection.AddOptions&lt;MyOptions&gt;().ValidateDataAnnotations()
【解决方案2】:

我使用一种技术来验证我的 netcore 应用程序中的数据注释,不是使用 IValidateOptions,而是实现自定义验证器,并将其注册为 PostConfigure

您可以在命名空间System.ComponentModel.DataAnnotations 中找到有价值的资产。

类似这样的:

    // Custom validator for data annotations
    public static class Validation {
        public static void ValidateDataAnotations<TOptions>(TOptions options) {
            var context = new ValidationContext(options);
            var results = new List<ValidationResult>();
            Validator.TryValidateObject(options, context, results, validateAllProperties: true);
            if (results.Any()) {
                var aggrErrors = string.Join(' ', results.Select(x => x.ErrorMessage));
                var count = results.Count;
                var configType = typeof(TOptions).Name;
                throw new ApplicationException($"Found {count} configuration error(s) in {configType}: {aggrErrors}");
            }
        }
    }

然后,你在你的组合根目录中注册这个静态方法(可能是Startup.cs):

    public void ConfigureServices(IConfiguration configuration, IServiceCollection serviceCollection) {
        // (...)

        serviceCollection.Configure<DatabaseOptions>(configuration.GetSection(nameof(DatabaseOptions)));

        // invalid configuration values will break at this point
        serviceCollection.PostConfigure<DatabaseOptions>(Validation.ValidateDataAnotations);
    }

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2019-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-07
  • 2019-11-29
  • 1970-01-01
相关资源
最近更新 更多