在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
2021-11-03 Fluent Validation 当有 Children 的时候, 它的返回是这样的 property name 会是一个 path 的形式. array 就配上 [n]. 如果我们有需求动态添加 error 的话, 就必须符合它的格式哦. 比如: var validator = new PersonValidator(); var person = new Person { Children = new List<Child> { new Child(), new Child() } }; var personResult = validator.Validate(person); for (int i = 0; i < person.Children.Count; i++) { var child = person.Children[i]; var childValidator = new ChildValidator(); var childResult = childValidator.Validate(child); foreach (var error in childResult.Errors) { var eExp = Expression.Parameter(person.GetType(), "e"); var eDotNameExp = Expression.Property(eExp, nameof(person.Children)); var lambda = Expression.Lambda(eDotNameExp, eExp); var propertyName = ValidatorOptions.Global.PropertyNameResolver(person.GetType(), person.GetType().GetProperty(nameof(person.Children)), lambda); error.PropertyName = $"{propertyName}[{i}].{error.PropertyName}"; personResult.Errors.Add(error); } } Console.WriteLine(JsonSerializer.Serialize(personResult.Errors.Select(e => new { e.PropertyName, e.ErrorMessage }), new JsonSerializerOptions { WriteIndented = true } )); 需要特别注意的是, PropertyName 必须经过正确的 ValidatorOptions.Global.PropertyNameResolver 处理. 第 1 个参数是 root class type, 第 2 个参数是 last depth PropertyInfo, 最后一个是从 Root 到 deepest propertyInfo 的路径 lambda 表达式 这样它才能 generate 到对的 Property Name FluentValidation parse expression 的源码是这样的 就这样看的话, 应该是没有 cover 到 Children[0].Name 这种 [0] 的处理的. 所以估计它是通过外部累加做到的. 所以使用 PropertyNameResolver 的时候, 可不要放入 [0] 这种 expression 哦.
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;
|
请发表评论