小弟初來乍到,分享一些工作學習中遇到的問題和解決方式,如有不准確或是有錯誤的地方,希望不吝賜教,謝過了。 --Dogtwo 起因: ABP 中異常處理的思路是很清晰的。一共五種類型的異常類。 AbpInitializationException用於封裝ABP初始化過程中出現的異常,只要拋出AbpIn ...
小弟初來乍到,分享一些工作學習中遇到的問題和解決方式,如有不准確或是有錯誤的地方,希望不吝賜教,謝過了。 --Dogtwo
起因:
ABP 中異常處理的思路是很清晰的。一共五種類型的異常類。
AbpInitializationException用於封裝ABP初始化過程中出現的異常,只要拋出AbpInitializationException異常就可以,無須做額外處理。這類異常往往是需要維護人員介入分析的。
其他四個異常都在AbpController中被集中處理,處理分為兩步:一,通過EventBus觸發異常事件,相應的異常處理函數則處理異常。二,針對AbpValidationException,UserFriendlyException和AbpAuthorizationException異常,Abp會將異常信息轉換為ErrorInfo,並以view或Json的形式返回給客戶端。
(以上內容摘自 HK Zhand大大的博客)
我們應使用UserFriendlyException來包裝我們自定義的異常,但UserFriendlyException拋出的異常存在一個問題:無法指定HttpStatus code,這樣前端收到的response中HttpStatus code均為500。對一部分前端語言或框架來說,這個狀態碼難以處理或不便於處理,因為約定5xx指示伺服器異常,不應再由前端進行Handle。
所以,我們希望更改Response中的HttpStatus code。
思路很簡單,我們利用filter去攔截異常,判斷其類型,若為我們自定義的異常則更改HttpStatus code。
Filter部分代碼
1 public class MyExceptionFilter : IExceptionFilter 2 { 3 public ILogger Logger { get; set; } 4 5 public MyExceptionFilter() 6 { 7 Logger = NullLogger.Instance; 8 } 9 10 public void OnException(ExceptionContext context) 11 { 12 if (!(context.Exception is MyException)) 13 { 14 return; 15 } 16 17 HandleAndWrapException(context); 18 } 19 20 private void HandleAndWrapException(ExceptionContext context) 21 { 22 context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; 23 24 var myException = (MyException)context.Exception; 25 26 context.Result = new ObjectResult( 27 new AjaxResponse( 28 new ErrorInfo(myException.ErrorCode, myException.Message) 29 ) 30 ); 31 32 LogHelper.LogException(Logger, context.Exception); 33 34 context.Exception = null; //Handled! 35 } 36 }
然後再StartUp文件中添加filter
1 services.Configure(mvcOptions => 2 { 3 mvcOptions.Filters.AddService(typeof(MyExceptionFilter )); 4 });
運行發現,filter並不能catch住我們拋出的異常,甚至OnException方法體都沒進入。
原來,ABP本身實現了幾個filter,包括Authorization Filter, Audit Action Filter, Validation Action Filter, Unit of Work Action Filter, Exception Filter和Result Filter.
造成我們的自定義filter無法正常執行的原因就是Exception Filter.
ABP官方文檔介紹為:
AbpExceptionFilter is used to handle exceptions thrown from controller actions. It handles and logs exceptions and returns a wrapped response to the client.
This only handles object results, and not view results. Actions returning any object, JsonResult or ObjectResult will be handled. Actions are not handled if they return a view or any other result type implementing IActionsResult. It is recommend that you use the built-in UseExceptionHandler extension method defined in the Microsoft.AspNetCore.Diagnostics package to handle view exceptions.
Exception handling and logging behaviour can be changed using the WrapResult and DontWrapResult attributes for methods and classes.
除此之外, ABPExceptionFilter還會觸發AbpHandledExceptionData eventbus event.且,經由ABPExceptionFilter處理之後,會將異常信息轉換為ErrorInfo,並以view或Json的形式返回給客戶端。所以當response通過ABPExceptionFilter之後便不再包含Exception了,自然我們的Filter捕捉不到了。
由此,想到兩個解決方法
一 經由Event bus再次將異常拋出(很明顯,如果是為瞭解決本問題的話,邏輯不通,很差的解決方式。已經被catch的異常再拋出來被自定義的filter去處理,七擒孟獲)
但為了熟悉這部分的代碼邏輯還是做了一下實現。
1 public class MyExceptionHandler : IEventHandler, ITransientDependency 2 { 3 public ILogger Logger { get; set; } 4 5 public MyExceptionHandler() 6 { 7 Logger = NullLogger.Instance; 8 } 9 10 public void HandleEvent(AbpHandledExceptionData eventData) 11 { 12 Logger.Info(eventData.Exception.ToString()); 13 throw eventData.Exception; 14 } 15 }
如果是為了別的一些功能,上面利用EventBus來處理的方式也可以借鑒。
二是禁用ABPExceptionFilter改為使用我們自己的filter
禁用ABPExceptionFilter
除上面聲明Filter之外還需要在Configure中app.UseMvc之後添加
// Remove AbpExceptionFilter var ops = app.ApplicationServices.GetRequiredService<IOptions<MvcOptions>>().Value; var abpExceptionFilter = ops.Filters.FirstOrDefault(f => (f as ServiceFilterAttribute)?.ServiceType == (typeof(AbpExceptionFilter))); ops.Filters.Remove(abpExceptionFilter);
此方法也有不好的地方,即除我們自定義的異常外,其他異常並不能再由ABPExceptionFilter來處理。
改良最佳版:
我們的最根本的目的其實是更改HttpStatusCode,所以即便Exception被ABPExceptionFilter 封裝成ErrorInfo來說對我們影響並不大,只要能讓我們的自定義Filter catch住異常即可,因此,在Configuresevice中註冊filter時聲明順序即可。
options.Filters.AddService(typeof(myExceptionFilter), order: 1);
另:由ABPExceptionFilter介紹可知,它並不會catch住所有類型的異常,如果想對任意類型的異常進行處理,使用middleware可能會更好。
參考內容:
github:
https://github.com/aspnetboilerplate/aspnetboilerplate/issues/1550
https://github.com/aspnetboilerplate/aspnetboilerplate/issues/3280