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>();

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM