如何在 IValidateOptions<T> 实现中触发模型验证?

How to trigger model validation inside IValidateOptions<T> implementation?

我有一个 .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 方法中触发验证?

我使用一种技术在我的 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);
    }

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


基于我创建了自己的基于数据注释的选项验证器

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>>();