分享方式:


處理未處理的例外狀況 (C#)

作者:Scott Mitchell

檢視或下載範例程式碼 \(英文\) (如何下載)

當生產環境中 Web 應用程式發生運行時錯誤時,請務必通知開發人員並記錄錯誤,以便在稍後的時間點進行診斷。 本教學課程提供 ASP.NET 處理運行時間錯誤的概觀,並查看一種方式,讓自定義程式代碼在未處理的例外狀況反升至 ASP.NET 運行時間時執行。

簡介

在 ASP.NET 應用程式中發生未處理的例外狀況時,它會反升至 ASP.NET 運行時間,這會引發 Error 事件並顯示適當的錯誤頁面。 錯誤頁面有三種不同類型的錯誤頁面:運行時間錯誤黃色螢幕 (YSOD) ;例外狀況詳細數據 YSOD;和自定義錯誤頁面。 在 上述教學課程 中,我們已將應用程式設定為針對遠端使用者使用自定義錯誤頁面,以及針對在本機瀏覽的使用者使用例外狀況詳細數據 YSOD。

使用符合網站外觀和操作的易記自定義錯誤頁面,是預設運行時間錯誤 YSOD 的慣用,但顯示自定義錯誤頁面只是完整錯誤處理解決方案的一部分。 在生產環境中的應用程式發生錯誤時,請務必讓開發人員收到錯誤通知,讓開發人員能夠復原例外狀況的原因並加以解決。 此外,應該記錄錯誤的詳細數據,以便稍後的時間點檢查和診斷錯誤。

本教學課程說明如何存取未處理的例外狀況詳細數據,以便記錄這些例外狀況並通知開發人員。 下列兩個教學課程會探索一些設定之後的錯誤記錄連結庫,會自動通知開發人員運行時間錯誤並記錄其詳細數據。

注意

如果您需要以某種唯一或自定義的方式處理未處理的例外狀況,本教學課程中檢查的資訊最有用。 如果您只需要記錄例外狀況並通知開發人員,則使用錯誤記錄連結庫是一種做法。 接下來的兩個教學課程提供兩個這類連結庫的概觀。

引發事件時執行Error程序代碼

事件會提供物件一種機制,表示已發生有趣的專案,以及讓另一個物件執行回應中的程序代碼。 身為 ASP.NET 開發人員,您習慣在事件方面思考。 如果您想要在訪客按下特定 Button 時執行某些程式代碼,您可以建立該 Button 事件的 Click 事件處理程式,並將程式代碼放在該處。 假設每當發生未處理的例外狀況時,ASP.NET 運行時間就會引發其 Error 事件 ,它會遵循記錄錯誤詳細數據的程式代碼會在事件處理程式中執行。 但如何為 Error 事件建立事件處理程式?

事件Error是 類別中許多事件之HttpApplication一,這些事件會在要求存留期間於 HTTP 管線的特定階段引發。 例如,HttpApplication類別的事件BeginRequest會在每個要求開始時引發;當安全性模塊識別要求者時,就會引發其AuthenticateRequest事件。 這些 HttpApplication 事件可讓頁面開發人員在要求存留期的各種時間點上執行自定義邏輯。

事件的事件處理程式 HttpApplication 可以放在名為 Global.asax的特殊檔案中。 若要在網站中建立此檔案,請使用名為的全域應用程式類別範本 Global.asax,將新專案新增至網站的根目錄。

反白顯示全域點 A S A X 檔案的 Sceenshot。

圖 1:將 新增 Global.asax 至 Web 應用程式
(按鍵即可檢視完整大小的映像)

Visual Studio 所建立檔案的內容和結構 Global.asax 會根據您使用 Web 應用程式專案 (WAP) 或網站專案 (WSP) 而稍有不同。 使用 WAP 時,會 Global.asax 實作為兩個不同的檔案 - Global.asaxGlobal.asax.cs。 檔案 Global.asax 不包含 @Application 參考 .cs 檔案的指示詞;感興趣的事件處理程式定義於檔案中 Global.asax.cs 。 針對 WSP,只會建立單一檔案, Global.asax而且事件處理程式會在 區塊中 <script runat="server"> 定義。

Global.asax由 Visual Studio 的全域應用程式類別範本在 WAP 中建立的檔案包含名為 Application_BeginRequestApplication_AuthenticateRequestApplication_Error的事件處理程式,分別為 HttpApplication 、 和事件BeginRequestAuthenticateRequestError事件處理程式。 另外還有名為 Application_StartSession_StartApplication_EndSession_End的事件處理程式,這些事件處理程式會在 Web 應用程式啟動時、新工作階段啟動時、應用程式結束時,以及工作階段結束時分別引發的事件處理程式。 Global.asax Visual Studio 在 WSP 中建立的檔案只Application_Error包含 、Application_StartSession_StartApplication_EndSession_End 事件處理程式。

注意

部署 ASP.NET 應用程式時,您必須將 Global.asax 檔案複製到生產環境。 在 Global.asax.cs WAP 中建立的檔案不需要複製到生產環境,因為此程式代碼會編譯成專案的元件。

Visual Studio 的全域應用程式類別範本所建立的事件處理程式並不詳盡。 您可以藉由命名事件處理程式 來新增任何 HttpApplication 事件的事件處理程式 Application_EventName。 例如,您可以將下列程式代碼新增至 檔案,Global.asax以建立 事件的事件處理程式AuthorizeRequest

protected void Application_AuthorizeRequest(object sender, EventArgs e)
{
    // Event handler code
}

同樣地,您可以移除不需要的全域應用程式類別範本所建立的任何事件處理程式。 在本教學課程中 Error ,我們只需要事件的事件處理程式;請放心地從 Global.asax 檔案中移除其他事件處理程式。

注意

HTTP 模組 提供另一種方式來定義事件的事件處理程式 HttpApplication 。 HTTP 模組會建立為類別檔案,可直接放在 Web 應用程式專案中,或分隔成個別類別庫。 由於這些模組可以分成類別庫,因此 HTTP 模組提供更有彈性且可重複使用的模型來建立 HttpApplication 事件處理程式。 雖然檔案 Global.asax 專屬於其所在 Web 應用程式,但 HTTP 模組可以編譯成元件,此時將 HTTP 模組新增至網站就如同在資料夾中卸載元件 Bin ,並在中 Web.config註冊 Module 一樣簡單。 本教學課程不會探討如何建立和使用 HTTP 模組,但下列兩個教學課程中使用的兩個錯誤記錄連結庫會實作為 HTTP 模組。 如需 HTTP 模組優點的詳細資訊,請參閱 使用 HTTP 模組和處理程式建立可插入式 ASP.NET 元件

擷取未處理例外狀況的相關信息

此時,我們有具有事件處理程式的 Application_Error Global.asax 檔案。 當這個事件處理程式執行時,我們需要通知開發人員錯誤並記錄其詳細數據。 若要完成這些工作,我們必須先判斷所引發例外狀況的詳細數據。 使用 Server 物件的 GetLastError 方法來 擷取導致 Error 引發事件之未處理例外狀況的詳細數據。

protected void Application_Error(object sender, EventArgs e)
{
    // Get the error details
    HttpException lastErrorWrapper = 
        Server.GetLastError() as HttpException;
}

方法GetLastError會傳回 類型 Exception的物件,這是 .NET Framework 中所有例外狀況的基底類型。 不過,在上述程序代碼中,我正在將 傳GetLastErrorHttpException回的Exception物件轉換成物件。 Error如果因為處理 ASP.NET 資源期間擲回例外狀況而引發事件,則擲回的例外狀況會包裝在 內HttpException。 若要取得預先加密 Error 事件的實際例外狀況, InnerException 請使用 屬性。 Error如果事件因為 HTTP 型例外狀況而引發,例如不存在頁面的要求,則會擲回 ,HttpException但它沒有內部例外狀況。

下列程式代碼會使用 GetLastErrormessage 來擷取觸發 Error 事件之例外狀況的相關信息,並將 儲存 HttpException 在名為 lastErrorWrapper的變數中。 然後它會將原始例外狀況的類型、訊息和堆疊追蹤儲存在三個字串變數中,檢查 是否 lastErrorWrapper 為在 HTTP 型例外狀況的情況下觸發 Error 事件的實際例外狀況) (,或是否只是處理要求時擲回之例外狀況的包裝函式。

protected void Application_Error(object sender, EventArgs e)
{
    // Get the error details
    HttpException lastErrorWrapper = 
        Server.GetLastError() as HttpException;

    Exception lastError = lastErrorWrapper;
    if (lastErrorWrapper.InnerException != null)
        lastError = lastErrorWrapper.InnerException;

    string lastErrorTypeName = lastError.GetType().ToString();
    string lastErrorMessage = lastError.Message;
    string lastErrorStackTrace = lastError.StackTrace;
}

此時,您有撰寫程式代碼所需的所有資訊,以將例外狀況的詳細數據記錄到資料庫數據表。 您可以針對感興趣的每個錯誤詳細數據建立包含數據行的資料庫數據表- 類型、訊息、堆疊追蹤等等,以及其他有用的資訊片段,例如要求頁面的 URL 和目前登入的用戶名稱。 在事件處理程式中 Application_Error ,您接著會連接到資料庫,並將記錄插入數據表中。 同樣地,您可以新增程式碼,以透過電子郵件警示開發人員錯誤。

接下來兩個教學課程中檢查的錯誤記錄連結庫提供這類功能,因此不需要自行建置此錯誤記錄和通知。 不過,為了說明 Error 引發事件,以及 Application_Error 事件處理程式可用來記錄錯誤詳細數據並通知開發人員,讓我們新增程式代碼,以在發生錯誤時通知開發人員。

發生未處理的例外狀況時通知開發人員

在生產環境中發生未處理的例外狀況時,請務必警示開發小組,以便評估錯誤並判斷需要採取的動作。 例如,如果聯機到資料庫時發生錯誤,則您必須再次檢查您的 連接字串,而且或許是向您的 Web 主控公司開啟支援票證。 如果因為程式設計錯誤而發生例外狀況,可能需要新增額外的程式代碼或驗證邏輯,以避免未來發生這類錯誤。

命名空間中的 System.Net.Mail .NET Framework 類別可讓您輕鬆地傳送電子郵件。 類別MailMessage代表電子郵件訊息,並具有、FromSubjectBodyAttachmentsTo屬性。 SmtpClass是用來使用指定的 SMTP 伺服器傳送MailMessage物件;SMTP 伺服器設定可以在 中的 元素Web.config file<system.net>以程式設計方式或宣告方式指定。 如需在 ASP.NET 應用程式中傳送電子郵件訊息的詳細資訊,請參閱我的文章:從 ASP.NET Web Pages 網站傳送 Email,以及 System.Net.Mail 常見問題

注意

專案 <system.net> 包含傳送電子郵件時,類別所使用的 SmtpClient SMTP 伺服器設定。 您的 Web 主控公司可能會有一部 SMTP 伺服器,可用來從應用程式傳送電子郵件。 如需您應該在 Web 應用程式中使用的 SMTP 伺服器設定資訊,請參閱 Web 主機的支援區段。

將下列程式代碼新增至事件處理程式, Application_Error 以在發生錯誤時傳送電子郵件給開發人員:

void Application_Error(object sender, EventArgs e)
{
    // Get the error details
    HttpException lastErrorWrapper = 
        Server.GetLastError() as HttpException;

    Exception lastError = lastErrorWrapper;
    if (lastErrorWrapper.InnerException != null)
        lastError = lastErrorWrapper.InnerException;

    string lastErrorTypeName = lastError.GetType().ToString();
    string lastErrorMessage = lastError.Message;
    string lastErrorStackTrace = lastError.StackTrace;

    const string ToAddress = "support@example.com";
    const string FromAddress = "support@example.com";
    const string Subject = "An Error Has Occurred!";
    
    // Create the MailMessage object
    MailMessage mm = new MailMessage(FromAddress, ToAddress);
    mm.Subject = Subject;
    mm.IsBodyHtml = true;
    mm.Priority = MailPriority.High;
    mm.Body = string.Format(@"
<html>
<body>
  <h1>An Error Has Occurred!</h1>
  <table cellpadding=""5"" cellspacing=""0"" border=""1"">
  <tr>
  <tdtext-align: right;font-weight: bold"">URL:</td>
  <td>{0}</td>
  </tr>
  <tr>
  <tdtext-align: right;font-weight: bold"">User:</td>
  <td>{1}</td>
  </tr>
  <tr>
  <tdtext-align: right;font-weight: bold"">Exception Type:</td>
  <td>{2}</td>
  </tr>
  <tr>
  <tdtext-align: right;font-weight: bold"">Message:</td>
  <td>{3}</td>
  </tr>
  <tr>
  <tdtext-align: right;font-weight: bold"">Stack Trace:</td>
  <td>{4}</td>
  </tr> 
  </table>
</body>
</html>",
        Request.RawUrl,
        User.Identity.Name,
        lastErrorTypeName,
        lastErrorMessage,
        lastErrorStackTrace.Replace(Environment.NewLine, "<br />"));

    // Attach the Yellow Screen of Death for this error   
    string YSODmarkup = lastErrorWrapper.GetHtmlErrorMessage();
    if (!string.IsNullOrEmpty(YSODmarkup))
    {
        Attachment YSOD = 
            Attachment.CreateAttachmentFromString(YSODmarkup, "YSOD.htm");
        mm.Attachments.Add(YSOD);
    }

    // Send the email
    SmtpClient smtp = new SmtpClient();
    smtp.Send(mm);
}

雖然上述程式代碼相當冗長,但大量程式代碼會建立 HTML,該 HTML 會出現在傳送給開發人員的電子郵件中。 程式代碼一開始會參考 HttpException 方法傳 GetLastError 回的 (lastErrorWrapper) 。 要求引發的實際例外狀況是透過 lastErrorWrapper.InnerException 擷取,並指派給變數 lastError。 類型、訊息和堆疊追蹤資訊是從擷取並 lastError 儲存在三個字串變數中。

接下來, MailMessage 會建立名為 mm 的物件。 電子郵件本文是 HTML 格式,並顯示所要求頁面的 URL、目前登入用戶的名稱,以及類型、訊息和堆疊追蹤) 之 (例外狀況的相關信息。 類別的 HttpException 其中一個非經常性專案,就是您可以呼叫 GetHtmlErrorMessage 方法,產生用來建立例外狀況詳細數據黃色螢幕 (YSOD) 的 HTML。 這個方法可用來擷取例外狀況詳細數據 YSOD 標記,並將其新增至電子郵件作為附件。 一個警告字:如果觸發 Error 事件的例外狀況是 HTTP 型例外狀況, (例如要求不存在的頁面) 則 GetHtmlErrorMessage 方法會傳回 null

最後一個步驟是傳送 MailMessage。 這是藉由建立新的 SmtpClient 方法並呼叫其 Send 方法來完成。

注意

在 Web 應用程式中使用此程式碼之前,您會想要將 和 FromAddress 常數中的ToAddress值從 support@example.com 變更為錯誤通知電子郵件應該傳送到和來源的任何電子郵件位址。 您也必須在中的 <system.net>Web.config區段中指定 SMTP 伺服器設定。 請洽詢您的 Web 主機提供者,以判斷要使用的 SMTP 伺服器設定。

每當發生錯誤時,開發人員就會傳送摘要錯誤的電子郵件訊息,並包含 YSOD。 在上一個教學課程中,我們藉由流覽 Genre.aspx 並透過querystring傳入無效 ID 的值,例如 Genre.aspx?ID=foo,來示範運行時間錯誤。 Global.asax造訪具有檔案的頁面會產生與上一個教學課程相同的用戶體驗 - 在開發環境中,您將繼續看到例外狀況詳細數據黃色死畫面,而在生產環境中,您會看到自定義錯誤頁面。 除了這個現有的行為之外,開發人員還會傳送電子郵件。

圖 2 顯示造訪 Genre.aspx?ID=foo時收到的電子郵件。 電子郵件本文摘要說明例外狀況資訊,而 YSOD.htm 附件會顯示例外狀況詳細數據 YSOD 中顯示的內容 (請參閱 圖 3) 。

顯示傳送給開發人員的電子郵件螢幕快照。

圖 2:每當發生未處理的例外狀況時,開發人員就會傳送 Email 通知
(按鍵即可檢視完整大小的映像)

顯示電子郵件通知包含例外狀況詳細數據 Y S O D 作為附件的螢幕快照。

圖 3:Email 通知包含例外狀況詳細數據 YSOD 作為附件
(按鍵即可檢視完整大小的映像)

使用自訂錯誤頁面呢?

本教學課程示範如何使用 Global.asaxApplication_Error 事件處理程式,在發生未處理的例外狀況時執行程序代碼。 具體而言,我們使用這個事件處理程式來通知開發人員錯誤;我們可以將其擴充,以記錄資料庫中的錯誤詳細數據。 事件處理程式的存在 Application_Error 不會影響用戶體驗。 它們仍會看到已設定的錯誤頁面,可能是錯誤詳細數據 YSOD、運行時間錯誤 YSOD 或自定義錯誤頁面。

使用自定義錯誤頁面時, Global.asax 自然會想知道是否需要檔案和 Application_Error 事件。 發生錯誤時,用戶會顯示自定義錯誤頁面,因此為什麼無法將程式代碼放在開發人員,並將錯誤詳細數據記錄到自定義錯誤頁面的程式代碼後置類別中? 雖然您可以確實將程式代碼新增至自定義錯誤頁面的程式代碼後置類別,但您無法存取在使用我們在上一個教學課程中探索的技術時觸發 Error 事件的例外狀況詳細數據。 GetLastError從自訂錯誤頁面呼叫 方法會Nothing傳回 。

此行為的原因是透過重新導向到達自定義錯誤頁面。 當未處理的例外狀況到達 ASP.NET 運行時間時,ASP.NET 引擎會引發其Error事件 (,它會執行Application_Error事件處理程式) ,然後發出 Response.Redirect(customErrorPageUrl)將使用者重新導向至自定義錯誤頁面。 方法 Response.Redirect 會以 HTTP 302 狀態代碼將回應傳送給用戶端,指示瀏覽器要求新的 URL,也就是自訂錯誤頁面。 然後瀏覽器會自動要求這個新頁面。 您可以分辨自定義錯誤頁面與產生錯誤的頁面分開要求,因為瀏覽器的網址列變更為自定義錯誤頁面 URL, (請參閱 圖 4) 。

此螢幕快照顯示發生錯誤時,瀏覽器會重新導向。

圖 4:發生錯誤時,瀏覽器會重新導向至自定義錯誤頁面 URL
(按鍵即可檢視完整大小的映像)

淨效果是伺服器回應 HTTP 302 重新導向時,發生未處理的例外狀況結束的要求。 自定義錯誤頁面的後續要求是全新的要求;此時,ASP.NET 引擎已捨棄錯誤資訊,此外,也無法將先前要求中未處理的例外狀況與自定義錯誤頁面的新要求產生關聯。 這就是為什麼從自定義錯誤頁面呼叫時傳回null的原因GetLastError

不過,在造成錯誤的相同要求期間,可能會執行自定義錯誤頁面。 方法會將 Server.Transfer(url) 執行傳送至指定的 URL,並在相同的要求內處理它。 您可以將事件處理程式中的 Application_Error 程式代碼移至自訂錯誤頁面的程式代碼後置類別,並將它 Global.asax 取代為下列程式代碼:

protected void Application_Error(object sender, EventArgs e)
{
    // Transfer the user to the appropriate custom error page
    HttpException lastErrorWrapper = 
        Server.GetLastError() as HttpException;

    if (lastErrorWrapper.GetHttpCode() == 404)
    {
        Server.Transfer("~/ErrorPages/404.aspx");
    }
    else
    {
        Server.Transfer("~/ErrorPages/Oops.aspx");
    }
}

現在當發生未處理的例外狀況時,事件處理程式會 Application_Error 根據 HTTP 狀態代碼,將控件傳輸至適當的自訂錯誤頁面。 因為已傳輸控制件,所以自定義錯誤頁面可以透過存取未處理的例外狀況資訊 Server.GetLastError ,並可通知開發人員錯誤並記錄其詳細數據。 呼叫 Server.Transfer 會停止 ASP.NET 引擎,將使用者重新導向至自定義錯誤頁面。 相反地,自定義錯誤頁面的內容會以產生錯誤的頁面回應傳回。

摘要

當 ASP.NET Web 應用程式中發生未處理的例外狀況時,ASP.NET 運行時間會 Error 引發事件並顯示已設定的錯誤頁面。 我們可以透過建立 Error 事件的事件處理程式,通知開發人員錯誤、記錄其詳細數據,或以其他方式處理錯誤。 有兩種方式可以建立 HttpApplication 事件的事件處理程式,例如 Error:在檔案中 Global.asax ,或從 HTTP 模組建立。 本教學課程示範如何在檔案中Global.asax建立Error事件處理程式,以透過電子郵件訊息通知開發人員錯誤。

如果您需要以某種唯一或自定義的方式處理未處理的例外狀況,建立 Error 事件處理程式會很有用。 不過,建立您自己的 Error 事件處理程式來記錄例外狀況,或通知開發人員不是最有效率的時間使用,因為已有可用且容易使用可在幾分鐘內設定的錯誤記錄連結庫。 接下來的兩個教學課程會檢查兩個這類連結庫。

快樂的程序設計!

深入閱讀

如需本教學課程中討論之主題的詳細資訊,請參閱下列資源: