循序漸進學.Net Core Web Api開發系列【14】:異常處理

来源:https://www.cnblogs.com/seabluescn/archive/2018/07/22/9346789.html
-Advertisement-
Play Games

本篇介紹異常處理的知識。由於異常處理的技術應用並不複雜,本篇更多討論異常處理的一些理論知識,包括一些原則、約定和建議。 ...


系列目錄

循序漸進學.Net Core Web Api開發系列目錄

 本系列涉及到的源碼下載地址:https://github.com/seabluescn/Blog_WebApi

 

一、概述

本篇介紹異常處理的知識。由於異常處理的技術應用並不複雜,本篇更多討論異常處理的一些理論知識,包括一些原則、約定和建議。

 

二、異常處理的基本原則

在Win32API編程中是沒有異常處理機制的,函數一般都是通過返回一個BOOL型的狀態碼來表達處理是否成功,比如需要通過ID取得一個實體信息,需要這樣定義:

BOOL GetArticleByID(string ID,out Article article);

當調用失敗時(函數返回false),其實調用者是不知道失敗的原因的,如果需要知道原因,那就要返回一個int類型來表達狀態,-1表示成功,其他都是錯誤碼,這種函數對調用者而言簡直是噩夢。

.NET Framework中採用異常處理機制後,情況就好多了,上面的方法定義如下:

Article GetArticleByID(string ID);

看到這樣的定義,基本上不要看文檔也能明白這個方法的含義,另外所有可能失敗的情況都通過異常來進行報告。

所以,對於調用者而言,所有與期望不符的結果都可以認為是“異常”。

 對於異常的處理,有幾個基本原則:

1、只處理(catch)預計可能會發生的異常 

      在代碼中,我們只處理我們預計可能會發生的異常,比如要把一個字元串轉換為數字,我們預計可能會發生FormatException異常,那麼我們就Catch該異常,並提供處理辦法。

      這裡的異常應該是我們有能力處理的,其實每一行代碼我們都預計可能發生OutMemoryException的異常 ,但這個異常發生時,應用是沒有能力處理的,請不要catch它。

2、絕對不要catch根異常Exception

      這個原則和上面的原則其實是很類似的,catch了根異常表示你有能力處理所有未知異常,而且以同一種方式來處理,顯然是不合適的。

     由於考慮不周,我沒有考慮到某個異常,又不允許我catch根異常,實際運行時應該果然報了一個之前沒有預料的異常怎麼辦?很簡單,把這個異常加上就可以了。發生這種事情是因為編程者的經驗不足造成的,不能因為這個原因破壞異常處理的原則。

3、如果方法還有調用者,應該對異常進行封裝

      如果我們是寫類庫相關的代碼,主要是提供服務給消費者調用的,最好對捕捉到的異常進行封裝,給出和調用者重新約定的異常類型。比如我們在DAO層把所有捕捉到的異常處理完成後重新拋出一個DBOperateException,並提供相關信息。Control層在調用DAO時相對就簡單了,只需處理DBOperateException並把信息(或處理過的信息)報告給View就可以了。

下麵我們會以一些實例描述我們是如何遵守和打破這些原則的。

 

三、在WebApi開發中的異常處理

 我們要設計一個Controller,實現通過ID來獲取實例對象的功能,由於異常無法通過Http協議進行傳送,所以我們定義了一個ResultObject的返回類型,用於向客戶端傳送調用結果。

   public class ResultObject
    {
        public ResultObject()
        {
            state = ResultState.Success;
            ExceptionString = "";
            result = null;
        }

        public ResultState state { get; set; }
        public String ExceptionString { get; set; }

        public Object result { get; set; }
    }

    public enum ResultState
    {
        Success,
        Exception
    }

具體的Controller設計如下: 

public ResultObject GetArticleByID(string id)
        {
            try
            {
                int idn = int.Parse(id);

                Article article = _context.Articles
                    .AsNoTracking()
                    .Where(a => a.ID == id)
                    .Single();

                return new ResultObject
                {
                    result = article
                };
            }
            catch (System.FormatException ex)
            {
                _logger.LogError(ex.Message + "\n" + ex.StackTrace);

                return new ResultObject
                {
                    state = ResultState.Exception,
                    ExceptionString = "id必須為數字"
                };
            }
            catch (System.InvalidOperationException ex)
            {
                _logger.LogError(ex.Message + "\n" + ex.StackTrace);

                return new ResultObject
                {
                    state = ResultState.Exception,
                    ExceptionString = "未查詢到預料的數據"
                };
            }
            catch(MySql.Data.MySqlClient.MySqlException ex)
            {
                _logger.LogError(ex.Message + "\n" + ex.StackTrace);

                return new ResultObject
                {
                    state = ResultState.Exception,
                    ExceptionString = "資料庫異常"
                };
            }
        }

對於上述代碼,我們預料到可能用戶會輸入字元串而不是數字,也能預料到可能查詢不到結果,所以就截獲了這兩個異常。對於ToList這樣的操作,沒有查詢到數據會返回NULL,不會報異常,所以就不應該catch InvalidOperationException。另外,我們可能預料到會發生無法連接資料庫的異常,在此也處理了,由於資料庫連接異常可能在每個方法調用時都可能發生。建議提供為統一異常處理。

 

四、全局未處理異常

設計一個全局異常處理的中間件:

 public class UnifyExceptionMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;

        public UnifyExceptionMiddleware(RequestDelegate next, ILogger<UnifyExceptionMiddleware> logger)
        {
            _next = next;
            _logger=logger;
        }

        public async Task Invoke(HttpContext context)
        {
            ResultObject result =null;

            try
            {
                await _next(context);
            }
            catch(MySql.Data.MySqlClient.MySqlException ex)
            {
                _logger.LogError(ex.Message + "\n" + ex.StackTrace);

                result = new ResultObject
                {
                    state = ResultState.Exception,
                    ExceptionString = "資料庫異常"
                };
            }
            catch(Exception ex)
            {
                _logger.LogError($"系統發生未處理異常:{ex.StackTrace}");

                result = new ResultObject
                {
                    state = ResultState.Exception,
                    ExceptionString = "系統發生未處理異常"
                };                
            }

            context.Response.StatusCode = 200;
            context.Response.ContentType = "application/json; charset=utf-8";
            context.Response.WriteAsync(JsonConvert.SerializeObject(result));
        }
    }

    public static class VisitLogMiddlewareExtensions
    {
        public static IApplicationBuilder UseUnifyException(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<UnifyExceptionMiddleware>();
        }
    }

使用該中間件

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public IConfiguration Configuration { get; }
       // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {          
            loggerFactory.AddNLog();  
            app.UseUnifyException();            
            app.UseMvcWithDefaultRoute();            
        }
    }

異常處理的中間件要放在MVC中間件之前,這樣就可以截獲Contriller內的未處理異常。

 

五、兩點思考

1、為什麼我們處理了根異常Exception

前面提到不要處理根異常,但這裡卻處理了,這是什麼情況?我們說不要處理根異常,是因為不希望某個方法掩蓋了問題,向上級報告一個虛假的狀態,但對於所有處理流程的最上級,可以適當違反該原則。

就應用程式而言,當發生未處理異常時,操作系統會接管該異常的處理,這是微軟推薦的做法,但我們還是常常會進行全局未處理異常的處理,彈出一個用戶看得懂的提示框,並登記一個異常報告。

對於WebApi而言,介面並不直接面對用戶,但由於異常機制無法通過Http協議進行傳輸,介面的調用者就是WebApi的最終用戶了,所有可以對根異常進行捕獲。

這裡有兩種選擇:

1)不捕獲根異常,出現未處理異常時,向調用者報500;

2)捕獲根異常,出現未處理異常時,向調用者報200,同時報告異常內容。

具體如何選擇,就不是一個技術問題了,主要看團隊的管理規定與約定。某些公司規定介面是不允許報500的,否則是要扣績效的,那隻能捕獲根異常了,畢竟績效最重要對吧。

 

2、異常發生時,應該報告給客戶端什麼樣的狀態碼?

 我們和前端約定使用ResultObject來返回調用狀態和結果,對於發生“異常”時應該返回什麼樣的狀態碼比較合適呢,這大致也有兩種選擇:

1)一律返回200,通過ResultObject報告介面,欄位不夠可以增加信息欄位;

2)通過狀態碼返回一些特殊的異常,比如:找不到資源返回404,認證失敗返回401等,未知異常報500等等。

對於WebApi而言推薦使用第一種模式。

  

附:Http Response 返回碼

HTTP協議狀態碼表示的意思主要分為五類,大體是: 

1××

  保留 

2××

  表示請求成功地接收 

3××

  為完成請求客戶需進一步細化請求

4××

  客戶錯誤 

5××

  伺服器錯誤 

 列舉一些常見的狀態碼: 

200 OK  指示客服端的請求已經成功收到,解析,接受。 

401 Unauthorized  如果請求需要用戶驗證。回送應該包含一個WWW-Authenticate頭欄位用來指明請求資源的許可權。 

403 Forbidden  伺服器接受請求,但是被拒絕處理。 

404 Not Found  伺服器已經找到任何匹配Request-URI的資源。 

500 Internal Server Error  伺服器遭遇異常阻止了當前請求的執行。 

502 Bad Gateway  無效網關。 

503 Service Unavailable  因為臨時文件超載導致伺服器不能處理當前請求。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原創 裸一篇圖的BFS遍歷,直接來圖: 簡單介紹一下BFS遍歷的過程: 以上圖為例子,從0開始遍歷,訪問0,按大小順序訪問與0相鄰的所有頂點,即先訪問1,再訪問2; 至此頂點0已經沒有作用了,因為其本身和與其所有相鄰的頂點都已被訪問,將其出隊列,我們用隊列 存儲已訪問過的頂點;然後順著隊列,訪問頂點 ...
  • 發現一個奇怪的現象,我以Python入門為題的隨筆文章莫名其妙有幾十的閱讀量,讓我有點尷尬,其實大家如果想學Python的話,可以關註一些公眾號,比如「一個程式員的日常」和「痴海」等等,他們都有Python方面的學習資源,而且他們也會推薦一些優質的公眾號供大家學習,關註一下,如果之後感覺不怎樣,取關 ...
  • IO流和Properties IO流 IO流是指電腦與外部世界或者一個程式與電腦的其餘部分的之間的介面。它對於任何電腦系統都非常關鍵, 因而所有 I/O 的主體實際上是內置在操作系統中的。單獨的程式一般是讓系統為它們完成大部分的工作。 在 Java 編程中,一直使用流的方式完成 I/O。所有 ...
  • 註:希望與各位讀者相互交流,共同學習進步。 ...
  • 三大結構 順序 分支 迴圈 三大結構 順序 分支 迴圈 三大結構 順序 分支 迴圈 三大結構 順序 分支 迴圈 三大結構 順序 分支 迴圈 三大結構 順序 分支 迴圈 三大結構 順序 分支 迴圈 三大結構 順序 分支 迴圈 三大結構 順序 分支 迴圈 分支 分支的基本語法 if 條件表達式: 語句1 ...
  • 本篇文章主要介紹委托的應用。 委托是大家最常見的語法了,但會用與精通之間的差距是巨大的。 一個程式員如果不能精通委托,那麼,他永遠無法成為高級程式員。 所以,讓我們把委托刻到血液里吧。 這樣,你才能稱為Developer。 ...
  • --> public class OtherConfigInfo : ConfigurationSection { /// /// 獲取配置信息 /// /// public static OtherConfigInfo GetConfig() { ... ...
  • 最近比較忙,博客很久沒更新了,很多博友問何時更新博文,因此,今天就花了點時間,寫了本篇文章,但願大家喜歡。 本篇文章不適合初學者,需要對ASP.NET MVC具有一定基礎。 本篇文章主要從ASP.NET MVC 基架角度去分析MVC框架是如何實現資源過濾,資源授權,感興趣的,歡迎閱讀。 相關文章,請 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...