如何使用 FluentValidation 验证特定规则集(通过 IncludeRuleSets)中的 属性(通过 IncludeProperties)?

How to validate a property (via IncludeProperties) within a specific ruleset (via IncludeRuleSets) with FluentValidation?

花了一天时间搜索答案。

我有以下验证器(请注意 AgeMin 有不同的规则 属性 取决于规则集。):

class WeirdValidator : AbstractValidator<TestClass>
{
    
    public WeirdValidator()
    {
        public const string CustomRuleSet = nameof(CustomRuleSet);

        RuleFor(instance => instance.AgeMin).NotEmpty().WithMessage("Empty error. {PropertyName}").ScalePrecision(2, 3).WithMessage("Scale error. {PropertyName}");
        RuleFor(instance => instance.FirstInt).NotEmpty().WithMessage("Empty error. {PropertyName}");

        RuleSet(CustomRuleSet, () => {
            RuleFor(instance => instance.AgeMin).Empty().WithMessage("{PropertyName} must be empty.");
            RuleFor(instance => instance.SecondInt).NotEmpty().WithMessage("{PropertyName} must not be empty.");
        });
    }
}

和以下测试class:

class TestClass
{
    public decimal AgeMin { get; set; }
    public int FirstInt { get; set; }
    public int SecondInt { get; set; }
}

FluentValidation 允许我们组合验证器:

                var result = validator.Validate(instanceToValidate, opt => {
                opt.IncludeRuleSets(WeirdValidator.CustomRuleSet);
                opt.IncludeProperties(nameof(instanceToValidate.AgeMin));});

但它并没有像我预期的那样工作。它不是规则的交集,而是后续操作:首先应用来自 CustomRuleSet 的所有规则,然后应用定义的 属性 的规则。我只需要验证仅选定规则集的选定 属性。 我相信有一个非常简单和优雅的解决方案,但它还没有照亮我的路。

问题: 如何验证特定规则集中的特定 属性?

经过几天的痛苦和流泪,我找到了一个看起来很糟糕的解决方案,但仍然有效。

var validator = new WeirdValidator();

var ruleDescriptor = validator.CreateDescriptor();
var rulesOfMember = ruleDescriptor.GetRulesForMember(nameof(instanceToValidate.AgeMin));                
var exactRule = rulesOfMember.FirstOrDefault(c => c.RuleSets.Contains(WeirdValidator.CustomRuleSet));

var errorList = exactRule.Validate(
   new ValidationContext<TestClass>(instanceToValidate, new PropertyChain(),
   new RulesetValidatorSelector(WeirdValidator.CustomRuleSet)));

主要思想是找到精确 属性 的精确规则并只执行一个。原来 IValidationRule 有自己的 Validate/ValidateAsync 方法。

如果有人知道better/more优雅的解决方案,欢迎您。

您可以创建自定义 IValidatorSelector,并在创建验证时使用它 context.You 可以使用 FluentValidation.ValidatorOptions.Global.ValidatorSelectors 中提供的工厂来创建所需的选择器。

public class FluentValidatorCompositeValidatorSelector : IValidatorSelector
    {
        private IEnumerable<IValidatorSelector> _selectors;

        public FluentValidatorCompositeValidatorSelector(IEnumerable<IValidatorSelector> selectors)
        {
            _selectors = selectors;
        }

        public bool CanExecute(IValidationRule rule, string propertyPath, IValidationContext context)
        {
            return _selectors.All(s => s.CanExecute(rule, propertyPath, context));
        }
    }

这将过滤所有不满足 A​​LL 选择器的规则。

我将整个东西添加到扩展方法中以简化其使用。希望将来会有更好的解决方案。这是扩展方法,如果有人出于某种原因需要它。

public static class ValidatorExtensions
{
    public static async Task<ValidationResult> ValidateAsync<T>(
        this IValidator<T> @this,
        T instance,
        IEnumerable<string> properties,
        IEnumerable<string> ruleSets,
        CancellationToken cancellationToken)
    {
        var memberNameValidator = ValidatorOptions
            .Global
            .ValidatorSelectors
            .MemberNameValidatorSelectorFactory(properties);

        var ruleSetValidator = ValidatorOptions
            .Global
            .ValidatorSelectors
            .RulesetValidatorSelectorFactory(ruleSets);

        var validationContext = new ValidationContext<T>(
            instance,
            new PropertyChain(),
            new FluentValidatorCompositeValidatorSelector(new[] {
                memberNameValidator,
                ruleSetValidator
            }));

        var validationResult = await @this.ValidateAsync(validationContext, cancellationToken);

        return validationResult;
    }
}