2020-09-17
在使用 when 的时候要留意一下, 不过是 true/false 里面的代码都会被执行, 它是先调用 action, setup 好 validation, 然后在 validation 的时候才去调用 when 看要不要处理.
所以不要把其它逻辑放进去哦
WhenAsync(async (a, _) => { await Task.Delay(1000); return false; }, () => { // 一定会进来 RuleFor(e => e.ID).NotEmpty(); });
2020-09-11
有时候会用到异步
https://docs.fluentvalidation.net/en/latest/async.html
比如 WhenAysnc, 记得哦, 如果有用到 WhenAsync 或者 MustAsync, 那么就需要调用 ValidateAsync 哦.
WhenAsync(async (e, cancellationToken) => { var isDebtor = await creditTermService.IsDebtorOrCreditorAsync(e.creditTerm); return !isDebtor; }, () => { RuleFor(e => e.creditLimit).Null(); });
2020-02-20
NotEmpty vs NotNull
public class SimpleTestData { public string? string1 { get; set; } public string string2 { get; set; } = ""; public int? number1 { get; set; } public int numbe2 { get; set; } }
json
{ "string1" : "", "string2" : "", "number1" : 0, "number2" : 0 }
result
"errors": { "numbe2": [ "'numbe2' must not be empty." ], "string1": [ "'string1' must not be empty." ], "string2": [ "'string2' must not be empty." ] }
int 如果是 nullable 的话, empty 只是验证 != null, 0 是 ok 的, 因为 default(int?) 是 null. 符合逻辑.
int 如果不是 nullable, empty 验证 != 0 因为 default(int) 是 0
但是 string 就不同了, 因为没有初始值, 所以 emptty 永远都会检查 "" empty string. 所以这个时候用 notnull 就比较合理了.
not empty 对 List<string> length = 0 也是会保护哦
2020-02-04
InclusiveBetween vs ExclusiveBetween
between 1 - 10, 没有说明 1 和 10 是不是 ok. (大部分情况下是)
如果 1 和 10 ok 那么就是 InclusiveBetween
如果是 1 和 10 不可以, 2...9 才可以的话,那么是 ExclusiveBetween
2019-10-24
新功能,child validation
从前需要特地开一个 child validator 来写逻辑, 现在不需要咯
public class CustomerValidator : AbstractValidator<Customer> { public CustomerValidator() { RuleForEach(x => x.Orders).ChildRules(order => { order.RuleFor(x => x.ProductName).NotNull(); order.RuleFor(x => x.Amount).GreaterThan(0); }); } }
2019-08-18
验证 decimal scale 和 precision
RuleFor(x => x.Amount).ScalePrecision(4, 9);
留意哦,1st param 是 scale 2nd 才是 precision
和 sql 是相反的. sql 是 decimal(9,4), 真奇葩...
issue : https://github.com/JeremySkinner/FluentValidation/issues/1008
目前它的验证也和 sql 不同, 比如 decimal(9, 4), 意思是前面最多 5 位数. 但是 fluent validation 没有左右验证的概念. 所以这个是通过的,然后你输入 sql 就会报错了.
作者说 9.0 可能修改这个. 让它和 sql 验证行为一致.
之前就有在 .net 时代介绍过了.
这个 dll 也支持 .net core 而且一直有人维护.
对比 data annotation 的 validation, 我越来越觉得这个 fluent 好用多了.
一堆 Attribute 在 property 上面真的很乱.
安装很容易
nuget : https://www.nuget.org/packages/FluentValidation.AspNetCore/
然后 startup.cs
services.AddMvc().AddFluentValidation(fvc => fvc.RegisterValidatorsFromAssemblyContaining<Startup>()).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
定义规则 : getting started refer : https://fluentvalidation.net/start
public class ProductPostData { public string code { get; set; } } public class ProductPostDataValidator : AbstractValidator<ProductPostData> { public ProductPostDataValidator() { RuleFor(product => product.code).NotEmpty(); } }
build in rule :
https://fluentvalidation.net/built-in-validators
complex 的做法 :
写一个 child validator, 然后 SetValidator. 一定要 set 哦, 不会自动的
RuleFor(o => o.Address).NotNull(); RuleFor(o => o.Address).SetValidator(new AddressValidator());
或者直接定义, 不过要记得另外处理 null 的情况哦, 还有一般的 If
RuleFor(o => o.Address).NotNull(); RuleFor(o => o.Address.text1).NotEmpty().When(o => o.Address != null);
也可以 if else 包起来
When(customer => customer.IsPreferred, () => { RuleFor(customer => customer.CustomerDiscount).GreaterThan(0); RuleFor(customer => customer.CreditCardNumber).NotNull(); }).Otherwise(() => { RuleFor(customer => customer.CustomerDiscount).Equal(0); });
这个很常用到 .Must()
refer : https://fluentvalidation.net/custom-validators#predicate-validator
custom valid
https://fluentvalidation.net/custom-validators
namespace Project.Validators { public static class CustomValidatorExtensions { public static IRuleBuilderOptions<T, string> IsKeatKeat<T>( this IRuleBuilder<T, string> ruleBuilder, string value) { return ruleBuilder.Must((rootObject, propertyValue, context) => { context.MessageFormatter.AppendArgument("value", value); return (propertyValue as string) == "keatkeat"; }).WithMessage("{PropertyValue} is not keatkeat {value}"); //return ruleBuilder.SetValidator(new IsKeatKeatValidator(value)); } } public class IsKeatKeatValidator : PropertyValidator { private readonly string Value; public IsKeatKeatValidator(string value) : base("{PropertyValue} is not keatkeat {value}") { Value = value; } protected override bool IsValid(PropertyValidatorContext context) { if (context.PropertyValue == null) return false; var propertyValue = context.PropertyValue as string; context.MessageFormatter.AppendArgument("value", Value); return propertyValue == "keatkeat"; } } }
RuleFor(p => p.code).NotEmpty().IsKeatKeat("keatkeat");
DI 和 async
public ProductPostDataValidator( ApplicationDbContext Db ) { //RuleFor(p => p.code).NotEmpty().IsKeatKeat("keatkeat"); RuleFor(p => p.code).NotEmpty().MustAsync(async (id, cancellation) => { var products = await Db.Products.ToListAsync(); return true; }); }
如果用 setValidator 那就要自己传进去了。
多一个例子, 不用匿名方法
public class SimpleTestDataValidator : AbstractValidator<SimpleTestData> { private readonly IHttpContextAccessor HttpContextAccessor; public SimpleTestDataValidator( IHttpContextAccessor httpContextAccessor ) { HttpContextAccessor = httpContextAccessor; RuleFor(p => p.age).NotEmpty().MustAsync(BeSomething); } private async Task<bool> BeSomething(int age, CancellationToken cancellation) { await Task.Delay(2000); return true; } }
array, not empty 就是 check null 和 count, 通过 foreach set 后面的
RuleFor(p => p.colors).NotEmpty().ForEach(c => c.SetValidator(new ProductColorPostDataValidator()));
错一个就停 CascadeMode, defualt 行为是会验证所有的错误.
refer https://fluentvalidation.net/start#setting-the-cascade-mode#setting-the-cascade-mode
RuleFor(p => p.age).NotEmpty();
RuleFor(p => p.age).Cascade(CascadeMode.StopOnFirstFailure).Must(BeSomething).MustAsync(BeSomethingAsync);
DependentRules 这个也是用于错就不要跑接下来的
RuleFor(p => p.name).NotEmpty(); RuleFor(p => p.datas).NotEmpty().DependentRules(() => { RuleForEach(p => p.datas).SetValidator(new DataValidator()).DependentRules(() => { RuleFor(p => p.datas).Must(v => false); }); });
也可以针对整个 validator
public class PersonValidator : AbstractValidator<Person> { public PersonValidator() { // First set the cascade mode CascadeMode = CascadeMode.StopOnFirstFailure; RuleFor(x => x.Surname).NotNull().NotEqual("foo"); RuleFor(x => x.Forename).NotNull().NotEqual("foo"); } }
甚至是全局 ( 注意这个是 static 属性来的,不需要通过 config options 来配置 )
ValidatorOptions.CascadeMode = CascadeMode.StopOnFirstFailure;