abp vnext 自定义验证过滤器(ValidateFilter)

首先准备好过滤类:ValidateFilter 和 异常处理类:ExceptionFilter

    public class ValidateFilter : IActionFilter, ITransientDependency
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        public void OnActionExecuting(ActionExecutingContext context)
        {
            if (!context.ModelState.IsValid)
            {
                var result = new ResponseModel();
                foreach (var item in context.ModelState.Values)
                {
                    if (item.ValidationState == ModelValidationState.Invalid)
                    {
                        result.Code = 500;
                        result.Message = item.Errors.FirstOrDefault().ErrorMessage;
                        break;
                    }
                }
                context.Result = new JsonResult(result);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        public void OnActionExecuted(ActionExecutedContext context)
        {

        }
    }
    public class ExceptionFilter : ExceptionFilterAttribute
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        public override void OnException(ExceptionContext context)
        {
            var response = new ResponseModel(false, context.Exception.Message);

            context.Result = new ContentResult
            {
                // 返回状态码设置为200,表示成功
                StatusCode = StatusCodes.Status500InternalServerError,
                // 设置返回格式
                ContentType = "application/json;charset=gb2312",
                Content = JsonConvert.SerializeObject(response)
            };

            context.ExceptionHandled = true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override async Task OnExceptionAsync(ExceptionContext context)
        {
            await Task.Run(() =>
            {
                var response = new ResponseModel(false, context.Exception.Message);

                context.Result = new ContentResult
                {
                    // 返回状态码设置为200,表示成功
                    StatusCode = StatusCodes.Status500InternalServerError,
                    // 设置返回格式
                    ContentType = "application/json;charset=utf-8",
                    Content = JsonConvert.SerializeObject(response)
                };

                context.ExceptionHandled = true;
            });
        }
    }

 然后在XxxHttpApiHostModule的ConfigureServices方法里面添加

            context.Services.AddMvc(options =>
            {
                options.Filters.Add<ExceptionFilter>();
                options.Filters.Add<ValidateFilter>();
            });

结果打断点没走这个自定义的ValidateFilter。
TMD,asp.net core 3.1用这种方式妥妥的,到了你abp vnext为啥不行了?你咋这么能呢?
曾尝试过放弃,但我不信解决不了这个问题。然后各种百度、谷歌,QQ群咨询。终于在热心人的帮助下,渐渐明白了个大概。

看源码是必须的。

abp vnext的参数验证走的是AbpValidationActionFilter这个过滤器

    public class AbpValidationActionFilter : IAsyncActionFilter, ITransientDependency
    {
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            //TODO: Configuration to disable validation for controllers..?

            if (!context.ActionDescriptor.IsControllerAction() ||
                !context.ActionDescriptor.HasObjectResult())
            {
                await next();
                return;
            }

            context.GetRequiredService<IModelStateValidator>().Validate(context.ModelState);
            await next();
        }
    }

其中红色代码非常重要。看到IModelStateValidator了没?找到它的实现:ModelStateValidator。代码:

    public class ModelStateValidator : IModelStateValidator, ITransientDependency
    {
        public virtual void Validate(ModelStateDictionary modelState)
        {
            var validationResult = new AbpValidationResult();

            AddErrors(validationResult, modelState);

            if (validationResult.Errors.Any())
            {
                throw new AbpValidationException(
                    "ModelState is not valid! See ValidationErrors for details.",
                    validationResult.Errors
                );
            }
        }

        public virtual void AddErrors(IAbpValidationResult validationResult, ModelStateDictionary modelState)
        {
            if (modelState.IsValid)
            {
                return;
            }

            foreach (var state in modelState)
            {
                foreach (var error in state.Value.Errors)
                {
                    validationResult.Errors.Add(new ValidationResult(error.ErrorMessage, new[] { state.Key }));
                }
            }
        }
    }

通过代码我们发现,在Validate方法里面抛出了AbpValidationException异常,最后格式很丑:

{
    "error": {
        "code": null,
        "message": "Your request is not valid!",
        "details": "The following errors were detected during validation.\r\n - 姓名不能为空\r\n - 用户名不能为空\r\n",
        "data": {},
        "validationErrors": [
            {
                "message": "姓名不能为空",
                "members": [
                    "fullName"
                ]
            },
            {
                "message": "用户名不能为空",
                "members": [
                    "userName"
                ]
            }
        ]
    }
}

这种错误信息抛给用户无疑非常不友好。因为我在Dto模型上面添加了特性/注解,我就要返回我自己定义的错误信息。你abp vnext不行啊。

说了这么多怎么解决问题呢?说了这么多怎么解决问题呢?说了这么多怎么解决问题呢?
因为 AbpValidationActionFilter 使用 ModelStateValidator 做的处理,那么我将源码中的类:ModelStateValidator,直接拷贝过来放到HttpApi.Host项目里面。
想到就立马验证,跟abp vnext的一模一样:

    public class ModelStateValidator : IModelStateValidator, ITransientDependency
    {
        public virtual void Validate(ModelStateDictionary modelState)
        {
            var validationResult = new AbpValidationResult();

            AddErrors(validationResult, modelState);

            if (validationResult.Errors.Any())
            {
                throw new AbpValidationException(
                    "ModelState is not valid! See ValidationErrors for details.",
                    validationResult.Errors
                );
            }
        }

        public virtual void AddErrors(IAbpValidationResult validationResult, ModelStateDictionary modelState)
        {
            if (modelState.IsValid)
            {
                return;
            }

            foreach (var state in modelState)
            {
                foreach (var error in state.Value.Errors)
                {
                    validationResult.Errors.Add(new ValidationResult(error.ErrorMessage, new[] { state.Key }));
                }
            }
        }
    }

经过断点测试,是走我拷贝来的ModelStateValidator类的。如果我在Validate方法顶部直接return,不做任何逻辑,就会走我自定义的过滤器ValidateFilter了。

到此结束了?还没有,这种方法可以实现,但总感觉没按照abp vnext的套路出牌。
那么我们就按abp vnext的套路来,直接修改ModelStateValidator的Validate方法

        public virtual void Validate(ModelStateDictionary modelState)
        {
            foreach (var state in modelState)
            {
                foreach (var error in state.Value.Errors)
                {
                    throw new AbpValidationException(error.ErrorMessage);
                }
            }
        }

这样抛出来的异常就是正常的了,比如异常的Message就是:“姓名不能为空”,而不是一大堆的错误信息了。

{
    "code": 500,
    "message": "姓名不能为空",
    "data": null,
    "serverTime": "2021-04-28 11:38:58"
}

到此结束了?还没有,还有一种更简单的方式,不需要自己处理ModelStateValidator类,而是给ValidateFilter添加顺序

            context.Services.AddMvc(options =>
            {
                options.Filters.Add<ExceptionFilter>();
                options.Filters.Add<ValidateFilter>(-1);
});

这样,过滤器ValidateFilter就会优先执行。而且不会走自己封装的ExceptionFilter了。跟AbpValidationActionFilter一点关系都没了。

到此结束了?还没有,还有一种常见的方式,先把AbpValidationActionFilter移除,再添加自定义的Filter,具体类似下面的代码:

var filterMetadata = options.Filters.FirstOrDefault(x => x is ServiceFilterAttribute attribute && attribute.ServiceType.Equals(typeof(AbpValidationActionFilter)));
// 移除 AbpValidationActionFilter
options.Filters.Remove(filterMetadata);
options.Filters.Add<ValidateFilter>();

 

posted @ 2021-04-28 11:59  屌丝大叔的笔记  阅读(2006)  评论(5编辑  收藏  举报