• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Asp.netcore学习笔记FluentValidation

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

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;

 





鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap