使用 FluentValidation 实现数据校验、验重

来源:https://blog.csdn.net/zl33842902/article/details/90313537

最近项目里用到了 FluentValidation 对网站用户输入的数据进行了验证,使用起来比较舒服,下面整理一下项目中集成的过程。

需要集成的项目是一个 asp.net core 2.1 版本的项目。第一步,安装 FluentValidation.AspNetCore,VS会自动安装依赖的 FluentValidation、DI 等包。安装完成后,找到你要验证的数据类,比如我这里是一个修改密码的场景,类名是 UserPassword:

public class UserPassword
{
/// <summary>
/// 用户名
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 旧密码
/// </summary>
public string OldPassword { get; set; }
/// <summary>
/// 新密码
/// </summary>
public string NewPassword { get; set; }
/// <summary>
/// 重复密码
/// </summary>
public string NewPasswordRe { get; set; }
}
找到类后,为这个类写验证规则类,需要继承 AbstractValidator<UserPassword> 泛型类,其中 UserPassword 泛型是你要验证的类。在验证类的构造方法里写验证规则,代码如下:

public class UserPasswordValid : AbstractValidator<UserPassword>
{
public UserPasswordValid()
{
CascadeMode = CascadeMode.StopOnFirstFailure;
RuleFor(x => x.UserName).NotNull().WithName("用户名");
RuleFor(x => x.OldPassword).NotEmpty().Length(4, 32).WithMessage("旧密码不能为空且长度必须符合规则");
RuleFor(x => x.NewPassword).NotEmpty().Length(4, 32).WithMessage("新密码不能为空且长度必须符合规则")
.Must(NewNotEqualsOld).WithMessage("新密码不能跟旧密码一样");
RuleFor(x => x.NewPasswordRe).NotEmpty().WithMessage("重复密码不能为空").Must(ReEqualsNew).WithMessage("重复密码必须跟新密码一样");
}

/// <summary>
/// 判断新旧密码是否一样
/// </summary>
/// <param name="model">实体对象</param>
/// <param name="newPwd">新密码</param>
/// <returns>结果</returns>
private bool NewNotEqualsOld(UserPassword model, string newPwd)
{
return model.OldPassword != newPwd;
}
/// <summary>
/// 判断新密码与重复密码是否一样
/// </summary>
/// <param name="model"></param>
/// <param name="newPwdRe"></param>
/// <returns></returns>
private bool ReEqualsNew(UserPassword model, string newPwdRe)
{
return model.NewPassword == newPwdRe;
}
}
FluentValidation 的语法很人性化,初次接触的人大概都能看懂。如 RuleFor(x => x.UserName).NotNull().WithName("用户名");
这句就是 UserName 这个字段不能为 null ,为 null 时,会报 “用户名”不应为 null,WithName 就是给字段指定一个名字。
NotEmpty 就是不能为空,包括 null ;WithMessage 就是不符合条件时的提示。

编写完验证规则,要在 StartUp 里注册一下。在 services.AddMvc 这个子句的后边(注意还是在这句中)加入AddFluentValidation。

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddFluentValidation(cfg =>
{
cfg.RegisterValidatorsFromAssemblyContaining<UserPasswordValid>();
cfg.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
});
其中 cfg.RegisterValidatorsFromAssemblyContaining<UserPasswordValid>(); 这句注册你的验证类,如果有多个,就每个都要注册。cfg.RunDefaultMvcValidationAfterFluentValidationExecutes = false; 这句是告诉程序,用 FluentValidation 验证完,不要再使用 Mvc 的验证了。

最后就是在控制器里写处理错误的代码了,FluentValidation 会把验证的结果写入 ModelState,我们拿 ModelState 来验证就可以。

public IActionResult Index([FromBody]UserPassword userPassword)
{
if (!ModelState.IsValid)
{
return Json(new { Success = false, Item = ModelState.GetErrors() });
}
else
{
return Json(new { Success = true });
}
}
其中 ModelState.GetErrors() 是我写的一个扩展方法,为了把错误返回给前端,代码如下:

public static class ExtMethods
{
public static List<ValidationError> GetErrors(this Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary ModelState)
{
var errors = new List<ValidationError>();
foreach (var pair in ModelState)
{
foreach (var error in pair.Value.Errors)
{
errors.Add(new ValidationError(pair.Key, error.ErrorMessage));
}
}
return errors;
}
public static string ToSingleString(this IEnumerable<ValidationError> validations)
{
return validations.Select(x => x.Message).ToStringDot();
}
}
/// <summary>
/// 数据验证错误
/// </summary>
public class ValidationError
{
public ValidationError(string name, string message)
{
this.Name = name;
this.Message = message;
}
public string Name { get; set; }
public string Message { get; set; }
}
代码中的 ToStringDot 方法是我写的扩展方法,功能和 string.Join 一样。我在前端使用的 element-ui 作为前端展示库,效果如下:

 

对于数据的基本验证这样就可以,但是我们还有一些数据验证逻辑是需要走数据库的,这时就需要我们把验证的方法告诉 FluentValidation。比如在另一个场景中,我需要验证当前数据和数据库现有数据是否重复,验证的方法在一个叫 RoleService 的服务里, RoleService 实现了 IRoleService 接口,并且使用 aspnetcore 自带的微软DI进行注入。 那么我们需要在 Validator 的构造方法里把 Service 注入进来并在 Must 方法里使用这个 Service 进行验证。

/// <summary>
/// 角色创建的验证器
/// </summary>
public class RoleEditDtoValidator : AbstractValidator<RoleEditDto>
{
public RoleEditDtoValidator(IRoleService roleService)
{
RuleFor(x => x.RoleId).NotEmpty().WithName("角色编码");
RuleFor(x => x.RoleName).NotEmpty().WithName("角色名称");
RuleFor(x => x).Must(x => roleService.ExistCheck(x.Model))
.When(x => !x.RoleId.NullOrEmpty())
.WithMessage("数据存在重复,请检查!");
}
}
然而这样在提示时,不知道提交的数据与数据库里哪条数据发生了重复,所以在提示里要把ID带出来。这个想了半天才想出办法来,代码如下:

/// <summary>
/// 角色创建的验证器
/// </summary>
public class RoleEditDtoValidator : AbstractValidator<RoleEditDto>
{
public RoleEditDtoValidator(IRoleService roleService)
{
RuleFor(x => x.RoleId).NotEmpty().WithName("角色编码");
RuleFor(x => x.RoleName).NotEmpty().WithName("角色名称");
RuleFor(x => x.WithExistId()).Must(x => { var rst = roleService.ExistCheck(x.Model, out var existId); x.ExistId = existId; return !rst; })
.When(x => !x.RoleId.NullOrEmpty())
.WithMessage((x, y) => "与 ID 为 " + y.ExistId.ToStringBy("、") + " 的数据存在重复,请检查!");
}
}
其中 NullOrEmpty 是我写的一个扩展方法,功能和 string.IsNullOrEmpty 一样, ToStringBy 也是我的扩展方法,和 string.Join 功能一样;这两个扩展方法在 xLiAd.ExtMethods 的包里有,有兴趣的童鞋可以 Nuget 一下。WithExistId 方法是生成一个新的类,功能就是把数据库返回的重复 ID 给带上。代码如下:

public class ModelWithExistId<T> where T : class
{
readonly T t;
public ModelWithExistId(T t)
{
this.t = t;
}
public T Model => t;
public IEnumerable<int> ExistId { get; set; }
}

public static class ExtMethods
{
public static ModelWithExistId<T> WithExistId<T>(this T model) where T : class
{
return new ModelWithExistId<T>(model);
}
}
前台提示如下

 

OK了,这样就差不多了,完美实现需求。

需要注意的是,当你传的参数不符合 MODEL 的规范时 ModelState.IsValid 也会为假,比如你接收 类中的一个字段是 int 型,但你传来个空字符串,ModelState.IsValid 就会报值无效。 但是这个无效和 FluentValidation 是没有关系的,这一点容易使人产生困惑,要注意一下。
————————————————
版权声明:本文为CSDN博主「zl33842902」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zl33842902/article/details/90313537

posted @ 2020-01-11 11:43  冰封的心  阅读(2283)  评论(0编辑  收藏  举报