空对象的FluentValidation规则

本文关键字:规则 FluentValidation 对象 | 更新日期: 2023-09-27 18:18:31

我一直在努力找出如何创建一个FluentValidation规则,检查它正在验证的对象的实例是否不为空,在验证它的属性之前。

我宁愿将这个空验证封装在Validator中,而不是在调用代码中进行。

请参阅下面的示例代码,其中需要注释所需的逻辑:

namespace MyNamespace
{
    using FluentValidation;
    public class Customer
    {
        public string Surname { get; set; }
    }
    public class CustomerValidator: AbstractValidator<Customer> 
    {
        public CustomerValidator() 
        {
            // Rule to check the customer instance is not null.
            // Don't continue validating.
            RuleFor(c => c.Surname).NotEmpty();
        }
    }
    public class MyClass
    {
        public void DoCustomerWork(int id)
        {
            var customer = GetCustomer(id);
            var validator = new CustomerValidator();
            var results = validator.Validate(customer);
            var validationSucceeded = results.IsValid;
        }
        public Customer GetCustomer(int id)
        {
            return null;
        }
    }
}

所以我的问题是我如何检查CustomerValidator()构造函数,客户的当前实例不是null中止进一步的规则处理,如果它是null?

空对象的FluentValidation规则

编辑2022-07-19
正如一些评论者指出的那样,请查看答案https://stackoverflow.com/a/52784357/1943以获取更新的实现。我还没有亲自审查过,但值得先试一试。

如果你使用的是旧版本,或者你喜欢怀旧,我下面的原始答案来自2013年。


你应该能够在你的CustomerValidator类中覆盖Validate方法。

public class CustomerValidator: AbstractValidator<Customer> 
{
    // constructor...
    
    public override ValidationResult Validate(Customer instance)
    {
        return instance == null 
            ? new ValidationResult(new [] { new ValidationFailure("Customer", "Customer cannot be null") }) 
            : base.Validate(instance);
    }
}

我现在无法真正测试,但是您可以尝试覆盖Validate,或者在When块中包含规则:

public CustomerValidator()
{
     When(x => x != null, () => {
         RuleFor(x => x.Surname).NotEmpty();
         //etc.
     });
}

对于使用版本>6.2.1的用户,您需要重写此签名,以实现与@chrispr:

相同的功能。
public override ValidationResult Validate(ValidationContext<T> context)
{
    return (context.InstanceToValidate == null) 
        ? new ValidationResult(new[] { new ValidationFailure("Property", "Error Message") })
        : base.Validate(context);       
}

///NETCORE-3.1示例
///fluentvalidator-9.5.0

public class Organisation
{ 
    public string Name { get; set; }
}
public class OrganisationValidator : AbstractValidator<Organisation>
{
    public OrganisationValidator()
    {
        RuleFor(x => x.Name).NotNull().MaximumLength(50);
    }
    protected override bool PreValidate(ValidationContext<Organisation> context, ValidationResult result)
    {
        if (context.InstanceToValidate == null) {
            result.Errors.Add(new ValidationFailure("", "org is null"));
            return false;
        }
        return base.PreValidate(context, result);
    }
}

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void ValidateWithNull()
    {
        var validator = new OrganisationValidator();
        Organisation organisation = null;
        var result = validator.Validate(organisation);
        // result.Errors[0].ErrorMessage == "org is null";
    }
}

这是一个较旧的帖子,但想更新答案,包括以下来自FluentValidation文档:

使用PreValidate

如果您需要在每次调用验证器时运行特定的代码,您可以通过覆盖PreValidate方法来实现。这个方法接受一个ValidationContext和一个ValidationResult,您可以使用它们来定制验证过程。

public class MyValidator : AbstractValidator<Person> {
  public MyValidator() {
    RuleFor(x => x.Name).NotNull();
  }
  protected override bool PreValidate(ValidationContext<Person> context, ValidationResult result) {
    if (context.InstanceToValidate == null) {
      result.Errors.Add(new ValidationFailure("", "Please ensure a model was supplied."));
      return false;
    }
    return true;
  }
}

https://docs.fluentvalidation.net/en/latest/advanced.html? prevalidate

由于上述解决方案不适合我(FluentValidation, Version=6.2.1.0 for Net45),我张贴我所做的。这只是ValidateAndThrow扩展方法的一个简单的替换/包装。

public static class ValidatorExtensions
{
    public static void ValidateAndThrowNotNull<T>(this IValidator<T> validator, T instance)
    {
        if (instance == null)
        {
            var validationResult = new ValidationResult(new[] { new ValidationFailure("", "Instance cannot be null") });
            throw new ValidationException(validationResult.Errors);
        }
        validator.ValidateAndThrow(instance);
    }
}

重写EnsureInstanceNotNull

protected override void EnsureInstanceNotNull(object instanceToValidate)
{
    if(instanceToValidate==null)
      throw new ValidationException("Customer can not be null");
}

通过Custom()的方式。当另一个字段的验证是基于当前字段的验证时,它也非常有用。

ruleBuilder.Custom((obj, context) =>
        {
            if (obj != null)
            {
                var propertyName = <field where should be validation>;
                context.AddFailure(propertyName, "'Your field name' Your validation message.");
            }
        });

我继承了fluent AbstractValidator并创建了一个NullReferenceAbstractValidator类:

public class NullReferenceAbstractValidator<T> : AbstractValidator<T>
{
    public override ValidationResult Validate(T instance)
    {
        return instance == null
            ? new ValidationResult(new[] { new ValidationFailure(instance.ToString(), "response cannot be null","Error") })
            : base.Validate(instance);
    }
}

,然后从该类继承,每个验证器都需要null引用检查:

public class UserValidator : NullReferenceAbstractValidator<User>

使用Cascade模式

下面是文档中的示例。

RuleFor(x => x.Surname).Cascade(CascadeMode.StopOnFirstFailure).NotNull().NotEqual("foo");

同样来自文档:

如果NotNull验证器失败,那么NotNull验证器将不存在执行。如果你有一个复杂的链,这特别有用其中每个验证器都依赖于前一个验证器才能成功。

你可以重写一个名为EnsureInstanceNotNull的虚拟方法,就像作者在这里推荐的那样

public class CustomerValidator: AbstractValidator<Customer> 
{
    public CustomerValidator() 
    {
        // Rule to check the customer instance is not null.
        RuleFor(c => c).NotNull();
        // Don't continue validating.
        RuleFor(c => c.Surname).NotEmpty();
    }
    protected override void EnsureInstanceNotNull(object instance) { }
}

普遍接受的PreValidate答案在这种情况下不起作用,如下所示:

如果您使用带有AbstractValidator派生的SetValidator,那么它如果属性值为空,则不会运行。这是故意的行为,因为AbstractValidator派生的目的是验证复杂类型的属性,如果实例是零。AbstractValidator不是为使用简单/基本类型或对象。现在,如果你想检查是否为空在这里,你可以在SetValidator调用之前使用一个NotNull规则(尽管在这种情况下,这似乎不是您想要的)。

对我来说唯一有效的方法是(我的例子):

RuleForEach(command => command.ProductDto.ProductInfos).NotNull().WithMessage("Custom Message").SetValidator(new ProductInfoValidator());

如果没有"NotNull()",空值将被跳过。