.net core自定义使用FluentValidation
本篇实使用FluentValidation时自动注册以及在注册后自动验证,无须在接口中添加验证代码的功能。
1.相应开发环境
- .net core 3.1
- Nuget包 FluentValidation 10.0.0
2.原校验过程
以下以Dmeo为例进行校验的过程,定义一个获取用户信息的接口,用FluentValidation对入参进行校验。
2.1 定义入参
定义一个入参UserRequest,定义4个属性,分别为姓名,性别,电话和地址。
/// <summary>
/// 用户入参请求
/// </summary>
public class UserRequest
{
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 性别
/// </summary>
public string Sex { get; set; }
/// <summary>
/// 电话
/// </summary>
public string Phone { get; set; }
/// <summary>
/// 地址
/// </summary>
public string Address { get; set; }
}
2.2 定义校验类
根据入参UserRequest定义一个校验类UserValidator来对参数模型进行校验。
public class UserValidator : AbstractValidator<UserRequest>
{
public UserValidator()
{
//遇到第一个失败即停止
CascadeMode = CascadeMode.Stop;
//校验姓名不能为空
RuleFor(i => i.Name).NotEmpty().WithMessage("姓名不能为空");
}
}
2.3 在接口中实现校验
在接口中需要先实例化一个校验类,然后将入参代入到校验类的Validate方法中进行校验,再对校验类验证后的接口进行处理。
[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
/// <summary>
///
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("Get")]
public ActionResult GetUser([FromBody] UserRequest input)
{
//实例化校验类
var userValidator = new UserValidator();
//验证
var validResult = userValidator.Validate(input);
//处理验证结果
if(!validResult.IsValid)
{
return new JsonResult(new { Code = 500, Msg = validResult.Errors[0].ErrorMessage });
}
return new JsonResult("已获取用户信息");
}
}
3. 现校验过程
原校验过程中对接口而言,需要每次初始化校验类,且对校验类结果进行处理。
现改后的思路为注入服务的方式对入参进行校验,在模型绑定后用过滤器对参数进行校验,在校验失败时直接返回,不进入接口内部的逻辑。
3.1 定义校验接口
定义一个服务IValidatorService,实现验证的功能
public interface IValidatorService
{
/// <summary>
/// 默认校验
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="message"></param>
/// <returns></returns>
bool Valid<T>(T value, out string message);
/// <summary>
/// 按规则校验
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="rule"></param>
/// <param name="message"></param>
/// <returns></returns>
bool Valid<T>(T value, string rule, out string message);
}
3.2 定义实现的具体校验
实现具体的IValidatorService,对校验类进行注册并验证。ValidatorService内部使用一个RegisterValidator的方法对校验类进行注册
/// <summary>
/// 自动注册服务
/// </summary>
private void RegisterValidator()
{
var assemblyConfig = new List<Assembly>();
//集中放置校验类时
assemblyConfig.Add(Assembly.GetAssembly(typeof(UserValidator)));
////分开放置校验类时
//foreach (string filePath in Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "CustomFluentValidationDemo.dll"))
//{
// assemblyConfig.Add(Assembly.LoadFrom(filePath));
//}
foreach (var assembly in assemblyConfig)
{
foreach (var i in assembly.GetTypes())
{
if (i.IsInterface) continue;
foreach (var type in i.GetInterfaces())
{
if (type.Name == "IValidator`1")
{
_validatorSet[type.GenericTypeArguments[0]] = (IValidator)Activator.CreateInstance(i);
}
}
}
}
}
ValidatorService内部使用Valid的方法对模型校验进行验证并返回错误结果
/// <summary>
/// 验证
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="message"></param>
/// <returns></returns>
public bool Valid<T>(T value, out string message)
{
message = string.Empty;
Type type = value.GetType();
string typeName = type.ToString();
if (!_validatorSet.ContainsKey(type))
{
message = $"未找到{value}相对应的验证类";
return false;
}
//验证途中如果没有 则新加入验证方法
if (!_validaBehaviorSet.ContainsKey(typeName))
{
_validaBehaviorSet.TryAdd(typeName, _validatorSet[type].Validate);
}
var context = new ValidationContext<T>(value);
ValidationResult result = _validaBehaviorSet[typeName](context);
if (result.IsValid) return true;
message = result.Errors?[0].ErrorMessage;
return false;
}
3.3 定义过滤器
定义一个参数验证的过滤器,在模型绑定后对参数进行校验,校验成功则进入接口,校验失败则返回错误。
/// <summary>
/// 参数校验
/// </summary>
public class ParamValidateAttribute : ActionFilterAttribute
{
/// <summary>
/// 规则名
/// </summary>
private readonly string _ruleName;
public ParamValidateAttribute(string ruleName = null)
{
_ruleName = ruleName;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var validatorService = context.HttpContext.RequestServices.GetService(typeof(IValidatorService)) as IValidatorService;
string message = string.Empty;
//依次对参数进行校验
foreach (var argument in context.ActionArguments)
{
if (!validatorService.Valid(argument.Value, _ruleName, out message))
{
//可根据项目结构自行定义返参
var result = new
{
Code = 500,
Msg = message
};
context.Result = new Microsoft.AspNetCore.Mvc.JsonResult(result);
break;
}
}
base.OnActionExecuting(context);
}
}
3.4 注册服务
向.net core的框架注入定义的服务,在startup的ConfigureServices方法中添加以下的服务注册方式。这步一定要用AddSingleton的模式进行注册,不能用Scope或者Transient的方式进行,需要将ValidatorService变成一个单例,其中的_validaBehaviorSet和_validaRuleBehaviorSet不会置空,能够将每次调用的校验保存下来,实现复用。
services.AddSingleton<IValidatorService, ValidatorService>();
3.5 使用服务
为了区分和之前定义的接口,新增两个接口,一个是删除用户和更新用户的接口,删除用户用的是默认配置的校验类,而更新接口使用的是校验类中另行配置的规则集,共两种方式。
[HttpPost("Delete"), ParamValidate()]
public ActionResult DeleteUser([FromBody] UserRequest input)
{
return new JsonResult("已删除用户信息");
}
[HttpPost("Update"), ParamValidate("Update")]
public ActionResult UpdateUser([FromBody] UserRequest input)
{
return new JsonResult("已更新用户信息");
}
在校验类中需要添加的部分代码
public class UserValidator : AbstractValidator<UserRequest>
{
public UserValidator()
{
//遇到第一个失败即停止
CascadeMode = CascadeMode.Stop;
//校验姓名不能为空
RuleFor(i => i.Name).NotEmpty().WithMessage("姓名不能为空");
RuleSet("Update", () =>
{
RuleFor(i => i.Name).NotEmpty().WithMessage("姓名不能为空");
RuleFor(i => i.Sex).NotEmpty().WithMessage("性别不能为空");
});
}
}
3.6 调用接口
使用postman对上述的两个接口进行调用,则接口结合FluentValidation正常运行。
调用接口api/user/delete时,缺失name,则会根据规则产生如下错误
{
"code": 500,
"msg": "姓名不能为空"
}
调用接口api/user/update时,缺失sex,则会根据规则产生如下错误
{
"code": 500,
"msg": "性别不能为空"
}
4. 总结
改变原来的方式后,少写了部分重复代码,更加方便。但同样要注意,避免出现未注册的情况出现。
FluentValidation的文档地址:https://docs.fluentvalidation.net/en/latest/start.html
demo地址:https://github.com/thePengLong/CustomFluentValidationDemo
如有问题,恳请指出。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示