storytelling
前几天看公司一个新项目使用了FluentValidation,大家都知道FluentValidation是一个非常强大的用于构建强类型验证规则的 .NET 框架,帮程序员解决了繁琐的校验问题,用起来非常爽,但我还是遇到了一件非常不爽的事情,如下代码所示:
public class UserInformationValidator : AbstractValidator<UserInformation>
{
public UserInformationValidator()
{
RuleFor(o => o.UserName).Length(2, 20).WithMessage("姓名长度输入错误");
RuleFor(o => o.Sex).Must(o=>o=="男"||o=="女").WithMessage("性别输入错误");
RuleFor(o => o.Age).ExclusiveBetween(0, 200).WithMessage("年龄输入错误");
RuleFor(o => o.Email).EmailAddress().WithMessage("邮箱输入错误");
}
}
static void Main(string[] args)
{
UserInformation userInformation = new UserInformation();
userInformation.UserName = "";
userInformation.Sex = "不男不女";
userInformation.Age = 2200;
userInformation.Email = "xxxxx";
UserInformationValidator validationRules = new UserInformationValidator();
var result= validationRules.Validate(userInformation);
if (!result.IsValid)
{
Console.WriteLine( string.Join(Environment.NewLine, result.Errors.Select(x => x.ErrorMessage).ToArray()));
}
}
我们每验证一个对象,就要新建一个类型的验证器 ,如上的UserInformationValidator ,虽然这样写逻辑上没有任何问题,但我有洁癖哈,接下来我们试着封装一下,嘿嘿,用更少的代码做更多的事情。
installation
在创建任何验证器之前,您需要在项目中添加对 FluentValidation.dll 的引用。最简单的方法是使用 NuGet 包管理器或 dotnet CLI。
Exploration of templated code encapsulation
Extract templated code into parent classes
仔细看上面的代码你会发现,我们每新建一个验证器,就必须要创建一个继承自AbstractValidator<T>的类,其中T是您希望验证的类的类型,封装一个验证器父类
public class CommonVaildator<T> : AbstractValidator<T>
{
}
Add verification rules
真正的业务逻辑是写在UserInformationValidator验证器里面的,而这块代码中只需要拿到RuleFor即可,其他的统一封装到父类中,对不对,我们按照这个思路代码,封装一个长度验证器规则。
First let's look at the prototype of RuleFor
public IRuleBuilderInitial<T, TProperty> RuleFor<TProperty>(Expression<Func<T, TProperty>> expression)
它的参数是一个Func委托,那么Expression是什么呢?
Experssion是一种表达式树!
表达式树是一种允许将lambda表达式表示为树状数据结构而不是可执行逻辑的代码。
在 C#中是Expression来定义的,它是一种语法树,或者说是一种数据结构。其主要用于存储需要计算、运算的一种结构,它只提供存储功能,不进行运算。通常Expression是配合Lambda一起使用,这里就不做过多的解释了!
Then we can easily encapsulate the length verifier rules!
public void LengthVaildator(Expression<Func<T, string>> expression, int min, int max, string Message)
{
RuleFor(expression).Length(min, max).WithMessage(Message);
}
In the same way, we can also go on to encapsulate the ** predicate validator rules *** Mailbox validator rules ** and so on
public void MustVaildator(Expression<Func<T, string>> expression ,Func<T,string, bool> expression2, string Message)
{
RuleFor(expression).Must(expression2).WithMessage(Message);
}
public void EmailAddressVaildator(Expression<Func<T, string>> expression, string Message)
{
RuleFor(expression).EmailAddress().WithMessage(Message);
}
Packaging verification method
上面我们把验证器封装好了,那么将 var result= validationRules.Validate(userInformation);这种验证方法封装一下不是手到擒来,代码如下:
public static string ModelValidator<T>(T source, AbstractValidator<T> sourceValidator) where T : class
{
var results = sourceValidator.Validate(source);
if (!results.IsValid)
return string.Join(Environment.NewLine, results.Errors.Select(x => x.ErrorMessage).ToArray());
else
return "";
}
Test encapsulated code
CommonVaildator<UserInformation> commonUserInformation = new CommonVaildator<UserInformation>();
commonUserInformation.LengthVaildator(o => o.UserName, 2, 30, "姓名长度输入错误");
commonUserInformation.MustVaildator(o => o.Sex, (user, _) => user.Sex =="男"||user.Sex=="女" , "性别输入错误");
commonUserInformation.ExclusiveBetweenVaildator(o=>o.Age,0, 200, "年龄输入错误");
commonUserInformation.EmailAddressVaildator(o => o.Email, "邮箱输入错误");
string msg= VaildatorHelper.ModelValidator(userInformation, commonUserInformation);
Console.WriteLine(msg);
Does the code look much simpler? I only encapsulate four verification rules here, and I won't encapsulate the rest here.
summary
The article comes from every detail in my work. This is also my impromptu packaging. If you have better packaging code, you are welcome to communicate. It is better to have fun alone than everyone else. This article will stop here, and I hope it will be helpful to you.