空对象的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?
编辑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()",空值将被跳过。