.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

如有问题,恳请指出。

.net core自定义使用FluentValidation

上一篇:JSON文件解析


下一篇:设计模式之抽象工厂模式