• 从零写一个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

  • 相关阅读:
    判断是否在可视区域
    格式化数字10000,为10,000
    把数组(含有30项分成5组)
    排序
    enzyme 学习一
    golang使用sftp连接服务器远程上传、下载文件
    golang使用ssh远程连接服务器并执行命令
    一文弄懂vlan、三层交换机、网关、DNS、子网掩码、MAC地址的含义
    golang的序列化与反序列化的几种方式
    golang命令行参数解析
  • 原文地址:https://www.cnblogs.com/NCoreCoder/p/10385459.html
走看看 - 开发者的网上家园