• 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.
     - 姓名不能为空
     - 用户名不能为空
    ",
            "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>();
  • 相关阅读:
    工作单元(UnitOfWork) 模式 (2) .NET Core
    工作单元(UnitOfWork) 模式 (1)
    WebAPI规范设计——违RESTful
    ASP.NET MVC / WebAPI 路由机制详解
    C#封装HttpClient工具类库(.NET4.5以上)
    centos7多网卡配置bond0 (mode6无需交换机做配置)
    linux windows 格式化一块大于2 TiB硬盘
    war包安装jenkins
    redis(一主两从三哨兵模式搭建)记录
    nginx + keepalived 主从模式
  • 原文地址:https://www.cnblogs.com/subendong/p/14713532.html
Copyright © 2020-2023  润新知