首先准備好過濾類: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>();