从零写一个Asp.net core手脚架(模型验证)

一个asp.net core项目,一定包含了各种的实体,在RESTful api里面,有很多的参数传递,不建立实体则大量的参数需要自定验证正确性,并且Action上面会写的密密麻麻的参数

在asp.net 2.0的时候,就推出了ModelState,顾名思义,这个是模型状态,用于验证实体对象的

如何模型验证

用法是在需要验证的地方打上继承了ValidationAttribute的特性,比如常见的RequiredAttribute,这个是验证对象是否存在

    /// <summary>
    /// Admin Request Model
    /// </summary>
    public class AdminRequestModel
    {
        /// <summary>
        /// User
        /// </summary>
        [Required]
        public string User { get; set; }

        /// <summary>
        /// Account
        /// </summary>
        [Required]
        public string Account { get; set; }
    }

这是一个实体

我们要验证的Model里是“User”属性和“Account”属性不能为空

写在Action上面

        /// <summary>
        /// Test Admin
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost("Admin")]
        public ResponseModel Admin(AdminRequestModel model)
        {
            return new AdminResponseModel()
            {
                User = model.User
            };
        }

在asp.net core 2.1之前版本的web api则改为

        /// <summary>
        /// Test Admin
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost("Admin")]
        public ResponseModel Admin([FromForm] AdminRequestModel model)
        {
            return new AdminResponseModel()
            {
                User = model.User
            };
        }

进入了Action的就是通过了基础模型验证的实体对象,数据库操作之类的业务验证,就在Action里面处理

怎么启用模型验证

全局过滤器

    /// <summary>
    /// Gold Filter Validate Model
    /// </summary>
    public class GoldModelFilter : IActionFilter
    {
        /// <summary>
        /// Action Before
        /// </summary>
        /// <param name="context"></param>
        public void OnActionExecuted(ActionExecutedContext context)
        {
        }

        /// <summary>
        /// Action After
        /// </summary>
        /// <param name="context"></param>
        public void OnActionExecuting(ActionExecutingContext context)
        {
            if (!context.ModelState.IsValid)
                throw new ValidateException(
                    context.ModelState.Values
                        .FirstOrDefault(item => item.Errors.Count > 0
                        )
                        .Errors.FirstOrDefault().ErrorMessage
                );
        }
    }

然后在

Startup.cs的ConfigureService内写的AddMvc改为

            services.AddMvc(options=> {
                options.Filters.Add<GoldModelFilter>();
            }));

我们不需要全局验证的则这样写

    public class ModelFilterAttribute:ActionFilterAttribute
    {
        /// <summary>
        /// Action Before
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuted(ActionExecutedContext context)
        {
            //leave out
        }

        /// <summary>
        /// Action After
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            //leave out
        }
    }

是不是很像,是的,就是一样的,只是一个是特性,一个是过滤器,内部的流程也差不多

ActionFilterAttribute还有很多东西,就暂不赘述了

我们在需要模型验证的Action上面打上特性就可以了

如何扩展自定义模型验证

CustomValidationAttribute,顾名思义这个是做自定义模型验证的,支持给属性、字段、方法、参数和实体对象验证的

他有两个参数,参数1是自定义的验证类Type,参数2是方法名

自定义验证类需要是公开类,自定义方法也是公开方法

方法参数1是object类型,参数2是ValidationContext类型

参数1是验证的内容,参数2是验证的上下文

包括

DisplayName 描述名

MemberName 成员名

ObjectInstance 整个验证的实体

ObjectType 整个验证的实体类型

还一个方法GetService

这个是获取服务的,依赖注入里面的服务,可以通过这个取出来

 

如果我们要对之前的AdminRequestModel这个实体验证User是否等于Account,不等于,则写一条错误日志,然后验证失败

    /// <summary>
    /// Admin Validate
    /// </summary>
    public class AdminValidate
    {
        /// <summary>
        /// Ordinary Validate
        /// </summary>
        /// <param name="value"></param>
        /// <param name="validationContext"></param>
        /// <returns></returns>
        public static ValidationResult Ordinary(object value, ValidationContext validationContext)
        {
            if (value is AdminRequestModel model)
            {
                if (!model.User.Equals(model.Account))
                {
                    var logger = validationContext.GetService(typeof(ILogger<AdminValidate>)) as ILogger<AdminValidate>;

                    logger.LogError("User not equals Account");

                    return new ValidationResult("User not equals Account");
                }

                return ValidationResult.Success;
            }

            return new ValidationResult("Type Error");
        }
    }

AdminRequestModel上面加一行[CustomValidation(typeof(AdminValidate), "Ordinary")]

这种是低复用的场景下,给特定的做验证的,如果我们这种验证很多,而且很多处都有相似的验证,那么可以写一个特性,继承ValidationAttribute

这个的验证的有两种场景,单纯的内容验证,没有功能性的,比如上面这种,验证失败,悄悄写一条错误日志,甚至发一个预警邮件

那么重构两个方法FormatErrorMessage和IsValid,前者返回错误信息,后者是验证的

除了验证,还有一些功能性的

那么请重构IsValid方法

代码和AdminValidate.Ordinary雷同

扩展

如果我们的过滤特性里面也有一些功能性呢?

    /// <summary>
    /// Register Service Action Filter
    /// </summary>
    public class ServiceActionFilterAttribute : ActionFilterAttribute
    {
        private ILogger<ServiceActionFilterAttribute> Logger { get; }

        /// <summary>
        /// Generate
        /// </summary>
        /// <param name="logger"></param>
        public ServiceActionFilterAttribute(ILogger<ServiceActionFilterAttribute> logger)
        {
            Logger = logger;
        }

        /// <summary>
        /// Action Before
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuted(ActionExecutedContext context)
        {
            //leave out
        }

        /// <summary>
        /// Action After
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            //leave out
        }
    }

这样是不能打在Action上面了

这个时候,我们就要掏出ServiceFilterArribute或者TypeFilterAttribute

        /// <summary>
        /// Test Admin
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost("AdminDIFilterAttibute")]
        [ServiceFilter(typeof(ServiceActionFilterAttribute))]
        public ResponseModel AdminDIFilterAttibute(AdminRequestModel model)
        {
            return new AdminResponseModel()
            {
                User = model.User
            };
        }

前者是Type必须注册在依赖注入容器里面的,后者是不需要的

 

项目地址:

https://github.com/htrlq/AspNetCoreTemplate

posted @ 2019-02-15 19:04  沉迷代码的萌新  阅读(511)  评论(0编辑  收藏  举报