IRONSOFTWAREHOME

C#最小API中的全域性錯誤處理

全球錯誤處理在C#精簡API中

Tim Corey

13m 30s

一個拋出未處理異常的Web API,預設會返回一種錯誤頁面,這幫助開發者在本地除錯,也幫助陌生人映射您的呼叫堆疊。 行號、型別名稱和源文件的路徑都會流回給請求者。擷取每個錯誤的正確方法是在端點處,但它僅能在未被記住的try/catch中工作。 一個全域處理器是捕捉端點遺漏的安全網。

在他的视频"全球錯誤處理在C#精簡API中"中,Tim Corey 構建了一個故意破壞的精簡API端點,演示了沒有保護的情況下返回的開發者錯誤頁面,然後設計 app.UseExceptionHandler 以攔截任何未捕捉到的異常,並以通用的500響應返回。他還加強了為什麼端點級處理是首選途徑:全域處理器是後備,不是策略。 任何希望確保沒有堆疊追蹤離開伺服器的精簡API交付者,都會在下面找到有關的中間件設置和設計理由。

建立具有破損端點的精簡API

[1:08 - 3:01] Tim 開始了一個全新的 .NET 8 ASP.NET Core Web API 專案,名為 ErrorDemoApp。專案模板選項接近預設:開啟 HTTPS,開啟 OpenAPI,無認證,頂級語句啟用,控制器複選框未選,因為這是一個精簡API。 生成的 Program.cs 保留了 Swagger,但天氣預報範例端點及其記錄被刪除,讓文件只呈現基本內容。

取樣的地方,他新增了一個 /demo 的單一端點,其目的是要失敗:

app.MapGet("/demo", () =>
{
    throw new Exception("This is a demo exception");
});
C#

using Ctrl+F5(開始無除錯)運行專案可防止 Visual Studio 除錯器截獲 throw,因此失敗像在實際 HTTP 呼叫者的面前一樣顯示。 Swagger打開,/demo端點是唯一可用的,並執行它返回500響應。 響應體包含例外型別、消息以及對 Program.cs 第18行的引用。

為什麼預設錯誤頁洩露實施細節

[3:01 - 5:00] 直接在瀏覽器中點擊 /demo(沒有 ?message= Swagger 包裝器)顯示的是開發人員例外頁面而不是 JSON 響應。 頁面渲染例外名稱、消息、發生拋出的位置的文件路徑和行號、原始例外詳細資訊,以及拋出上方的堆疊幀。 對於本地工作的開發者來說,這無疑是金礦。 對於其他任何人,這是程式碼庫的免費地圖。

Tim 的觀點直言不諱的介紹:這個頁面為幫助開發人員而存在,絕不應該傳遞給終端使用者。 它有時會被傳達出去的事實是設立全域處理器的重要原因。 即使是勤奮的團隊也終將錯過包裹每個端點在 異常處理中的某一個,錯過一個的代價是整個堆疊追蹤送到請求者手中。

首先在端點捕獲錯誤

[5:00 - 6:30] 在安裝全域處理器之前,Tim 用try/catch包裝了demo端點,以示範首選路徑。 處理器會為任何拋出的例外返回 Results.BadRequest(ex.Message)

app.MapGet("/demo", () =>
{
    try
    {
        throw new Exception("This is a demo exception");
    }
    catch (Exception ex)
    {
        return Results.BadRequest(ex.Message);
    }
});
C#

結果是一個僅攜帶消息字串的400。 沒有堆疊追蹤,沒有文件路徑,沒有行號。 消息本身是否應該曝露取決於應用; 對於公共API,即使是消息也可能泄露超出團隊想要的,在這種情況下,處理器會提供一個通用字串。 本地捕捉給予端點對調用者看到內容的全權控制,包含選擇在已知失敗模式下返回比500更具體的狀態碼。

這種模式無法做到的是捕捉端點遺忘包裹的東西。任何新程式碼路徑,任何更深層的重新拋出,任何 Task 在單獨的執行緒中拋出的都會繞過端點的try/catch。 這就是中間件填補的空隙。

連接UseExceptionHandler中間件

[6:30 - 10:00]app.UseHttpsRedirection() 行的下方,通過使用 app.UseExceptionHandler 註冊處理器。 接收建構器動作的重載暴露了底層管道,讓處理器可以明確設置響應形狀:

app.UseExceptionHandler(appError =>
{
    appError.Run(async context =>
    {
        context.Response.StatusCode = StatusCodes.Status500InternalServerError;
        context.Response.ContentType = "application/json";

        var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
        if (contextFeature is not null)
        {
            Console.WriteLine($"Error: {contextFeature.Error}");
        }

        await context.Response.WriteAsJsonAsync(new
        {
            StatusCode = context.Response.StatusCode,
            Message = "Internal Server Error"
        });
    });
});
C#

在該區塊中的幾項選擇很重要。 強制將狀態碼設為500意味著調用者無法從響應號中推斷任何資訊; 無論內部異常型別為何,表面看起來都是一樣的。 迫使內容型別為 application/json,與API其餘響應匹配,這讓客戶端使用單一解析器。 IExceptionHandlerFeature 暴露了原始例外,以便實際的處理器可以記錄它; Tim 在這裡使用 Console.WriteLine 作為該專案實際攜帶的任何記錄器的替身。

最終的 WriteAsJsonAsync 呼叫返回一個匿名物件,帶有狀態碼和通用消息。 主體只說明了故障的事實而無其他內容,這正是重點。 內部診斷應寫在日誌而非響應中。

測試已處理和未處理的路徑

[10:00 - 13:14] 有try/catch依然就位時,端點運行本地路徑:Swagger顯示一個攜帶"這是一個範例例外"的400。 中間件永遠不會看到拋出,因為catch塊先解析掉了它。這是Tim預設想要的設計:本地處理器完成其工作,全域處理器則應付時露面。

移除try/catch再次運行練習後備方案。 現在相同的請求返回一個500帶有JSON正文 { "statusCode": 500, "message": "Internal Server Error" }。 響應中沒有任何內容顯示異常出現的地點或型別。 然而,Visual Studio控制台窗口顯示的是通過 Console.WriteLine 佔位符記錄的原始例外文字,包括文件路徑和行號。 完整的診斷資訊保留在開發人員可以看到的地方; 響應保持在不會泄露的地方。

這個模式貫穿於具有驗證中間件、自定義認證或任何其他管道組件的精簡API。 異常處理器位於管道的早期,抓住從稍後階段傳遞上來的內容。

總結:深入防禦

[13:14 - 13:30] 本地處理和全域處理並不是替代品; 它們是層。 本地處理器在已知失敗模式下給予端點時,能作出有意義的響應。 全域處理器確保任何本地層未成功捕獲的內容產生的響應是一致的、通用又安全的。 基於控制器的API使用相同的原則,只是語法上有所調整,而精簡API形式是應該首先習慣的,因為表面積小到只需在單個文件中看到全貌。

結論

[13:14 - 13:30] 在精簡API中設置全域錯誤處理器的三個步驟:在管道早期註冊 UseExceptionHandler,在處理器內設置響應狀態和內容型別,並撰寫刻意通用的主體以避風格細節的泄密。 配合在最有可能失敗的程式碼路徑周圍加上本地try/catch塊,您就擁有了一個深入防禦模型,全域處理器是安全網而非戰略。

範例提示:當處理器調用實際記錄器時,傳遞整個異常物件(而不僅僅是消息),這樣結構化日誌管道便能捕獲型別、堆疊以及任何內部例外。 像 Serilog 這樣的記錄器將保留所有這些作為可查詢的屬性,這意味着觸發生產500的警報帶有足夠的上下文以在本地重現,而無需任何人重新運行請求。

觀看完整影片在他的 YouTube 頻道上,以獲得在10-Minute Training系列中構建生產就緒精簡API的更多見解。

Earn More by Sharing What You Love

Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Let's Stay in Touch!

Join our newsletter, you’ll get exclusive access on article updates. We value your privacy

Key in blue circle

立即免費取得 30 天試用金鑰

Your trial license will be sent to your email address

無任何限制。100% 解鎖。無需信用卡。

bullet_checked無需信用卡或建立帳號無任何限制。100% 解鎖。無需信用卡。
  • Logo Aetna
  • Logo NASA
  • Logo GE
  • Logo Porsche
  • Logo USDA
  • Logo Qatar
Join Millions of Engineers who’ve tried IronPDF
獲取您的無義務諮詢
填寫以下表格或發送電子郵件至sales@ironsoftware.com
您的詳細資訊將始終保密。
被全球數百萬工程師信任
Iron Software的客戶標誌
立即獲取您的30天試用金鑰
無需信用卡或帳戶建立