在上一篇教程中我為大家介紹了Web API中Filter的開發使用,其中講到ExceptionFilter時留了一個坑:ExceptionFilter只能截獲並處理Action執行過程中發生的異常,在Action執行過程之外如果出現異常,ExceptionFilter是無能為力的。
這些異常包括:
1、 Controller構造方法中出現的異常
2、 MessageHandlers中出現的異常
3、 路由過程中出現的異常
4、 Body在序列化/還原序列化過程中出現的異常
由此可以看出,ExceptionFilter只能解決ApiControler成功執行個體化後並執行Action期間出現的異常;為瞭解決這一個問題,在WEB API中除了ExceptionFilter外還引入了兩個針對異常記錄、處理的擴充點:
IExceptionLogger 和IExceptionHandler。
而這兩個擴充是作為Web API的管道組件進行註冊管理的,並且,他們有不同的分工:
IExceptionLogger作為異常日誌記錄組件,負責異常發生後的日誌記錄,他貫穿於整個Web API的生命週期中,在Web API架構裡,任何一個請求周期中出現任何一個未被捕獲/處理的異常都會首先進入這個異常日誌記錄管道進行異常Log記錄,在Web API中可以註冊多個IExceptionLogger執行個體負責不同的異常處理。
IExceptionHandler作為異常處理組件,負責異常發生後的處理工作,他處於異常處理管道的最末端,當IExceptionLogger組件進行一場記錄完畢、沒有相關的ExceptoinFilter進行異常處理時,才會最終調用ExceptionHandler進行異常處理,在Web API中,有且僅有一個ExceptionHandler進行異常的處理。
在Web API架構中給出了兩個基類:ExceptionLogger和ExceptionHandler,在使用ExceptionLogger基類時,他提供了ShouldLog虛方法,該方法在基類中被調用,其作用在於避免同一個異常被同一個ExceptionLogger執行個體重複記錄(如當後續的管道中該異常又被拋出,或者同一個ExceptionLogger對象不小心被註冊了兩次就會出現重複記錄的可能)我們也能複寫ShouldLog方法加入我們自己的異常記錄判斷邏輯以針對不同的情境進行不同的ExceptionLogger調用。如果有興趣可以反編譯一下ExceptionLogger基類看看,他使用了顯示介面實現,挺有意思的一個技巧。下面我們來看一個ExceptionLogger使用的例子:
public class ErroLogger : ExceptionLogger { public async Task LogAsync(ExceptionLoggerContext context, CancellationToken cancellationToken) { var sb = new StringBuilder(); //擷取Log組件 ILogger log = LogManager.GetCurrentClassLogger(); var request = context.Request; sb.AppendLine("URL:"); //擷取URL var url = request.RequestUri.ToString(); sb.AppendLine(url); log.Error(context.Exception,sb.ToString(),""); } public override bool ShouldLog(ExceptionLoggerContext context) { return context.Exception is DemoException && base.ShouldLog(context); } }
在這個例子中,我們重寫了ShouldLog,保證了這個ExceptionLogger只記錄DemoException這個類型的異常,並且也調用了基類方法,保證不會重複記錄同一個異常。在LogAsync方法中,我通過Log組件記錄了導致異常的請求URL,也記錄了異常資訊。
接下來我們要對這個組件進行註冊:
在App_Start/WebApiConfig.cs檔案中的Register方法中寫入
config.Services.Add(typeof(IExceptionLogger),new ErroLogger());
這樣,一個針對DemoException的異常記錄組件就開發完成並註冊完成了,當Web API執行管道中出現未處理的DemoException異常,均會調用則個組件進行記錄。
接下來我們來寫一個ExceptionHandler,在整個Web API架構中,ExceptionHandler只能提供一個執行個體,與ExceptionLogger一樣,我們可以繼承ExceptionHandler基類來簡化異常處理,在ExceptionHandler中也提供了ShouldHandle方法來判斷該異常是否應該處理,避免重複處理管道中其他環節重複拋出的異常。我們也同樣提供一個例子:
public class ErrorHandler : ExceptionHandler { public override async Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken) { if (context.Exception is DemoException) { context.Result = new ResponseMessageResult(context.Request.CreateResponse(HttpStatusCode.BadRequest,new {Message=context.Exception.Message})); } else { context.Result = new ResponseMessageResult(context.Request.CreateResponse(HttpStatusCode.InternalServerError,new {Message = "伺服器已被外星人綁架"})); } }}
在這個例子中,我們判斷了異常的類型,並根據不同的異常返回用戶端不同的響應內容和不同的HTTP狀態代碼。
然後在配置中註冊這個異常處理模組,在App_Start/WebApiConfig.cs檔案中的Register方法中寫入
config.Services.Replace(typeof(IExceptionHandler),new ErrorHandler());
這樣就替換了系統預設的ExceptionHandler,可以使用我們自訂的Handler進行異常的處理了。
在異常記錄、處理過程中,我們都碰到相應的異常上下文參數,我們能通過這個參數擷取當前請求的上下文,擷取請求、響應(小心有時會為空白哦)、捕獲到該異常的catch塊資訊等內容,我們可以利用這些資訊更好地描述、記錄、處理異常。
到這裡ExceptionLogger組件和ExceptionHandler組件簡單的開發就完成了。在開發的過程中我們可以看到,ExceptionLogger負責了全域的異常記錄,在Web API架構管道下出現未處理的異常ExceptionLogger都會進行捕獲、記錄。而ExceptionHandler和ExceptionFilter功能是有重疊的,那何時使用ExceptionHandler何時使用ExceptionFilter呢?我們可以將兩者的區別列出如下的表格:
|
ExceptionFilter |
ExceptionHandler |
範圍 |
Controller、Action |
全域 |
執行個體個數 |
無限制 |
全域唯一 |
作用條件 |
Controller執行個體化成功之後 |
Web API成功載入之後 |
經過上面的表我們可以看出,如果處理顆粒度細緻到Controller、Action層級時,ExceptionFilter處理起來會更得心應手,他已經能精確定位到某個Action,然後可以針對當前Action做定製開發。而ExceptionHandler範圍遠大於ExceptionFilter,他處理全域更有優勢。
關於Web API異常處理的相關內容我就講這麼一些,如果在文章中有任何不正確的地方或者疑問,歡迎大家為我指出。