模型验证

FluentValidation

FluentValidation 地址:https://github.com/FluentValidation/FluentValidation

模型验证库,它使用 lambda 表达式来构建强类型验证规则。

具体使用方法,可以参考官方文档:https://docs.fluentvalidation.net/en/latest/index.html

FlexibleCore 框架自动注入验证类,自动实现验证和返回验证信息,只需要按照规则编写验证类与验证规则,不需要自己定义 ValidationResult 接收验证结果,API 已经定义好统一的响应格式,并自动返回验证失败的错误信息。

简单的例子

创建一个类:

public class Customer
{
    public int Id { get; set; }
    public string Surname { get; set; }
    public string Forename { get; set; }
    public decimal Discount { get; set; }
    public string Address { get; set; }
}
                                

创建验证器类 CustomerValidator,一般为了可读性,验证器类的命名为:需要验证的类的类名 + Validator,并继承 AbstractValidator<验证类的类名> 来定义验证规则。

using FluentValidation;

public class CustomerValidator : AbstractValidator<Customer>
{
  public CustomerValidator()
  {
    RuleFor(customer => customer.Surname).NotNull();
  }
}
                                

自定义验证失败后的提示:

using FluentValidation;

public class CustomerValidator : AbstractValidator<Customer>
{
  public CustomerValidator()
  {
    RuleFor(customer => customer.Surname).NotNull().WithMessage("Surname 不能为空!");
  }
}
                                

也可以将同一属性的多个验证器连接在一起:

RuleFor(customer => customer.Surname).NotNull().NotEqual("foo");
                                

NotNull:确保指定的属性不为 null

RuleFor(customer => customer.Surname).NotNull();
                                

Null:确保指定的属性为 null

RuleFor(customer => customer.Surname).Null();
                                

NotEmpty:确保指定的属性不为 null、空字符串或空格(或值类型的默认值,例如 int 为 0)

RuleFor(customer => customer.Surname).NotEmpty();
                                

Empty:确保指定的属性为 null、空字符串或空格(或值类型的默认值,例如 int 为 0)

RuleFor(customer => customer.Surname).Empty();
                                

Equal:确保指定属性的值等于特定值(或不等于另一个属性的值)

RuleFor(customer => customer.Surname).Equal("Foo");
RuleFor(customer => customer.Surname).Equal(customer => customer.Forename);
                                

NotEqual:确保指定属性的值不等于特定值(或不等于另一个属性的值)

RuleFor(customer => customer.Surname).NotEqual("Foo");
RuleFor(customer => customer.Surname).NotEqual(customer => customer.Forename);
                                

Length:确保特定字符串属性的长度在指定范围内,不能确保 string 属性不为 null

RuleFor(customer => customer.Surname).Length(10, 250);
                                

MaximumLength:确保特定字符串属性的长度不超过指定值,不能确保 string 属性不为 null

RuleFor(customer => customer.Surname).MaximumLength(250);
                                

MinimumLength:确保特定字符串属性的长度大于指定的值,不能确保 string 属性不为 null

RuleFor(customer => customer.Surname).MinimumLength(10);
                                

GreaterThan:确保指定属性的值大于特定值(或大于另一个属性的值)

RuleFor(customer => customer.CreditLimit).GreaterThan(0);
RuleFor(customer => customer.CreditLimit).GreaterThan(customer => customer.MinimumCreditLimit);
                                

GreaterThanOrEqualTo:确保指定属性的值大于等于特定值(或大于等于另一个属性的值)

RuleFor(customer => customer.CreditLimit).GreaterThanOrEqualTo(1);
RuleFor(customer => customer.CreditLimit).GreaterThanOrEqualTo(customer => customer.MinimumCreditLimit);
                                

LessThan:确保指定属性的值小于特定值(或小于另一个属性的值)

RuleFor(customer => customer.CreditLimit).LessThan(100);
RuleFor(customer => customer.CreditLimit).LessThan(customer => customer.MaxCreditLimit);
                                

LessThanOrEqualTo:确保指定属性的值小于或等于特定值(或小于或等于另一个属性的值)

RuleFor(customer => customer.CreditLimit).LessThanOrEqualTo(100);
RuleFor(customer => customer.CreditLimit).LessThanOrEqualTo(customer => customer.MaxCreditLimit);
                                

EmailAddress:确保指定属性的值是有效的电子邮件地址格式

RuleFor(customer => customer.Email).EmailAddress();
                                

Phone:确保指定属性的值是有效的手机号,11 位,13\14\15\17\18 开头,半角数字

RuleFor(customer => customer.Phone).Phone();
                                

ZipCode:确保指定属性的值是有效的邮编,6 位半角数字,可以以 0 开头

RuleFor(customer => customer.ZipCode).ZipCode();
                                

UserName:用户名;6 到 20 位以字母开头,可包含(字母,数字,下划线,减号)

RuleFor(customer => customer.UserName).UserName();
                                

PassWord:密码;6到20位,必须包含数字、字母

RuleFor(customer => customer.PassWord).PassWord();
                                

PassWord:密码;6到20位,必须包含数字、字母

RuleFor(customer => customer.PassWord).PassWord();
                                

Tel:座机号;使用半角数字,支持以下两种方式:010-82312233/01082312267

RuleFor(customer => customer.Email).Tel();
                                

Company:公司名称;只能输入中文、英文、数字、空格、括号、横线、下划线

RuleFor(customer => customer.Company).Company();
                                

Chinese:确保输入值必须是中文

RuleFor(customer => customer.Chinese).Chinese();
                                

English:确保输入值必须是英文

RuleFor(customer => customer.English).English();
                                

Number:确保输入值必须是数字

RuleFor(customer => customer.Number).Number();
                                

ChineseIdentification:确保输入值必须符合身份证号格式

RuleFor(customer => customer.ChineseIdentification).ChineseIdentification();
                                

正则表达式验证:匹配某个正则表达式

RuleFor(customer => customer.Email).Matches("正则表达式");
                                

枚举验证:检查一个数值是否有效,可以包含在该枚举中。 当结果值无效时,这用于防止将数值强制转换为枚举类型

public enum ErrorLevel {
  Error = 1,
  Warning = 2,
  Notice = 3
}

RuleFor(x => x.ErrorLevel).IsInEnum();
                                

枚举名称验证:检查枚举名称是有有效

//区分大小写
RuleFor(x => x.ErrorLevelName).IsEnumName(typeof(ErrorLevel));
//不区分大小写
RuleFor(x => x.ErrorLevelName).IsEnumName(typeof(ErrorLevel), caseSensitive: false);
                                

数字范围(排除)验证:检查属性值是否在两个指定数字之间的范围内(排除)

RuleFor(x => x.Id).ExclusiveBetween(1,10);
                                

数字范围(包含)验证:检查属性值是否在两个指定数字之间的范围内(包含)

RuleFor(x => x.Id).InclusiveBetween(1,10);
                                

金额验证:检查属性值必须是金额

RuleFor(x => x.Amount).ScalePrecision(2, 4);
                                

数据库级别验证

数据库级别验证,需要在数据库访问层(例如 Repository 层),不然拿不到调用数据库的方法,建议配合 AutoMapper 使用。

注意 AbstractValidator 的构造方法不可使用动态获取的语句,如果需要在运行时获取值,需在 Must 方法里。

public class InsertUserValidator : AbstractValidator<User>
{
    public InsertUserValidator(Repository _repository)
    {
        //单个验证
        RuleFor(x => x.Phone).Must(Phone => _repository.Db.Queryable<User>().Where(x => x.Phone == Phone).Any() == false).WithMessage("手机号不可重复");

        //如果想要很多字段一起的验证
        RuleFor(user => user.Phone).Must((user, phone, context) => {
            User u = _repository.Db.Queryable<User>().Where(x => x.Phone == user.Phone || x.Email == user.Email).First();
            if (!u.IsNullOrEmpty())
            {
                List<string> message = new List<string>();
                if (u.Phone == user.Phone)
                    message.Add("手机号不可重复");
                if (u.Email == user.Email)
                    message.Add("邮箱不可重复");

                context.MessageFormatter.AppendArgument("MyMessage", message.ListStringToString());
                return false;

            }
            else
            {
                return true;
            }

        }).WithMessage("{MyMessage}");
    }
}
                                

自定义验证程序

实现自定义验证程序的最简单方法是使用方法 Must 方法。

public class Person 
{
    public IList<Person> Pets {get;set;} = new List<Person>();
}

// 为了确保列表中小于等于10个元素, 我们可以这样做 Must里面False 抛出异常
 RuleFor(x => x.Pets).Must(list => list.Count <= 10).WithMessage("The list must contain fewer than 10 items");

// 为了使这种逻辑可重用, 我们可以将其封装为扩展方法
public static IRuleBuilderOptions<T, IList<TElement>> ListMustContainFewerThan<T, TElement>(this IRuleBuilder<T, IList<TElement>> ruleBuilder, int num) {
    return ruleBuilder.Must(list => list.Count < num).WithMessage("The list contains too many items");
}

// 在这里,我们通过为 IRuleBuilder 创建扩展方法实现可重用逻辑,使用方法很简单
RuleFor(x => x.Pets).ListMustContainFewerThan(10);
                                

条件

When 和 Unless 方法可用于指定条件控制在规则应该执行。例如,仅当 IsPreferredCustomer 为 true 时,才会执行 CustomerDiscount 属性上的此规则:

RuleFor(customer => customer.CustomerDiscount).GreaterThan(0).When(customer => customer.IsPreferredCustomer);
//多个规则
When(customer => customer.IsPreferred, () => {
    RuleFor(customer => customer.CustomerDiscount).GreaterThan(0);
    RuleFor(customer => customer.CreditCardNumber).NotNull();
});
//带有Else
When(customer => customer.IsPreferred, () => {
    RuleFor(customer => customer.CustomerDiscount).GreaterThan(0);
    RuleFor(customer => customer.CreditCardNumber).NotNull();
}).Otherwise(() => {
    RuleFor(customer => customer.CustomerDiscount).Equal(0);
});
                                

设置级联模式

首先检查 Surname 属性是否不为 null,然后将检查其是否不等于字符串“ foo”。如果第一个验证器(NotNull)失败,则 NotEqual 仍将调用。可以通过指定级联模式来更改此设置 StopOnFirstFailure:

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

附属规则

确保某些规则仅在另一个规则完成后才执行。可以 DependentRules 用来执行此操作。

RuleFor(x => x.Surname).NotNull().DependentRules(() => {
    RuleFor(x => x.Forename).NotNull();
});