處理未處理的例外狀況 (VB)

作者:Scott Mitchell

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

當生產環境中 Web 應用程式發生運行時錯誤時,請務必通知開發人員並記錄錯誤,以便在稍後的時間點加以診斷。 本教學課程提供一個概概,說明 ASP.NET 如何處理執行階段錯誤,並查看自定義程式碼在未處理的例外被傳送至 ASP.NET 執行階段時執行的一種方法。

簡介

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

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

本教學課程示範如何存取未處理的例外狀況詳細數據,以便記錄這些例外狀況,並通知開發人員。 在這個教學課程之後的兩個教學課程會探索錯誤日誌庫,這些日誌庫經過一些設定後,會自動通知開發人員有運行時錯誤並記錄其詳細資訊。

注意

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

當事件被引發時執行代碼

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

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

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

使用全域應用程式類別範本,將新專案新增至網站的根目錄,其名稱為 Global.asax,以在您的網站中建立此檔案的螢幕快照。

圖 1:新增 Global.asax 至 Web 應用程式
(點擊查看完整圖片)

Visual Studio 所建立檔案 Global.asax 的內容和結構會根據您使用 Web 應用程式專案 (WAP) 或網站專案 (WSP) 稍有不同。 使用 WAP 時,會 Global.asax 實作為兩個不同的檔案 - Global.asax 和 Global.asax.vb。 檔案 Global.asax 只包含一個 @Application 指令,它參考到 .vb 檔案。所需的事件處理程式定義於 Global.asax.vb 檔案中。 若為 WSP,僅建立單一檔案,Global.asax,並在 <script runat="server"> 區塊中定義事件處理程式。

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

注意

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

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

Sub Application_AuthorizeRequest(ByVal sender As Object, ByVal e As EventArgs)
    ' Event handler code
End Sub

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

注意

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

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

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

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
    ' Get the error details
    Dim lastErrorWrapper As HttpException = _
        CType(Server.GetLastError(), HttpException)
End Sub

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

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

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
    ' Get the error details
    Dim lastErrorWrapper As HttpException = _
        CType(Server.GetLastError(), HttpException)

    Dim lastError As Exception = lastErrorWrapper
    If lastErrorWrapper.InnerException IsNot Nothing Then
        lastError = lastErrorWrapper.InnerException
    End If

    Dim lastErrorTypeName As String = lastError.GetType().ToString()
    Dim lastErrorMessage As String = lastError.Message
    Dim lastErrorStackTrace As String = lastError.StackTrace
End Sub

此時,您擁有將例外狀況詳細數據記錄至資料庫數據表所需的所有資訊。 您可以建立一個資料表,為感興趣的錯誤詳細資料建立欄位,例如型別、訊息、堆疊追蹤等,以及其他有用的資訊,例如所要求頁面的 URL 和目前登入使用者的名稱。 在事件處理程式中 Application_Error ,您接著會連線到資料庫,並將記錄插入數據表中。 同樣地,您可以新增程式碼,以透過電子郵件警示開發人員錯誤。

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

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

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

命名空間System.Net.Mail .NET Framework 類別可讓您輕鬆地傳送電子郵件。 MailMessage類 代表電子郵件訊息,並具有 To、From、Subject、Body 和 Attachments 等屬性。 是用來透過指定的 SMTP 伺服器傳送 物件,可以在 中的 元素中以程式設計或宣告方式指定 SMTP 伺服器設定。 如需在 ASP.NET 應用程式中傳送電子郵件訊息的詳細資訊,請參閱我的文章: 從 ASP.NET 網頁網站傳送電子郵件,以及 System.Net.Mail。

注意

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

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

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
    ' Get the error details
    Dim lastErrorWrapper As HttpException = _
        CType(Server.GetLastError(), HttpException)
    
    Dim lastError As Exception = lastErrorWrapper
    If lastErrorWrapper.InnerException IsNot Nothing Then
        lastError = lastErrorWrapper.InnerException
    End If

    Dim lastErrorTypeName As String = lastError.GetType().ToString()
    Dim lastErrorMessage As String = lastError.Message
    Dim lastErrorStackTrace As String = lastError.StackTrace

    Const ToAddress As String = "support@example.com"
    Const FromAddress As String = "support@example.com"
    Const Subject As String = "An Error Has Occurred!"

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

    'Attach the Yellow Screen of Death for this error
    Dim YSODmarkup As String = lastErrorWrapper.GetHtmlErrorMessage()
    If Not String.IsNullOrEmpty(YSODmarkup) Then
        Dim YSOD As Attachment = _
            Attachment.CreateAttachmentFromString(YSODmarkup, "YSOD.htm")
        mm.Attachments.Add(YSOD)
    End If

    ' Send the email
    Dim smtp As New SmtpClient()
    smtp.Send(mm)
End Sub

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

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

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

注意

在使用此程式碼於您的 Web 應用程式之前,您需要將 ToAddress 和 FromAddress 常數中的值從 support@example.com 更改為錯誤通知電子郵件的收件和發件位址。 您也必須在 <system.net> 中的 Web.config區段中指定 SMTP 伺服器設定。 請參閱您的 Web 主機提供者,以判斷要使用的 SMTP 伺服器設定。

只要發生錯誤,開發人員就會收到一封電子郵件,其中摘要了錯誤並包含 YSOD 詳情。 在上述教學課程中,我們示範了運行時錯誤,方法是瀏覽 Genre.aspx 並透過查詢字串傳入無效的 ID 值,例如 Genre.aspx?ID=foo。 瀏覽包含Global.asax檔案的頁面會產生與上一個教學課程相同的用戶體驗 - 在開發環境中,您會繼續看到例外狀況詳細數據的黃色死屏,而在生產環境中,您會看到客製化錯誤頁面。 除了此現有的行為之外,開發人員也會傳送電子郵件。

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

包含例外狀況資訊之電子郵件的螢幕快照。

圖 2:每當發生未處理的例外狀況時,開發人員就會傳送電子郵件通知
(點擊查看完整圖片)

開發人員收到電子郵件通知的螢幕快照,其中顯示未處理的例外狀況。

圖 3:電子郵件通知包含例外狀況詳細數據 YSOD 作為附件
(點擊查看完整圖片)

是否使用自訂錯誤頁面?

本教學課程示範如何在發生未處理的例外狀況時使用 Global.asax 和 Application_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)。

當發生錯誤時,重新導向至自定義錯誤頁面 U R L 的瀏覽器螢幕快照。

圖 4:發生錯誤時,瀏覽器會重新導向至自定義錯誤頁面 URL
(點擊查看完整圖片)

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

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

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
    ' Get the error details
    Dim lastErrorWrapper As HttpException = _
        CType(Server.GetLastError(), HttpException)

    If lastErrorWrapper.GetHttpCode() = 404 Then
        Server.Transfer("~/ErrorPages/404.aspx")
    Else
        Server.Transfer("~/ErrorPages/Oops.aspx")
    End If
End Sub

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

摘要

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

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

快樂程式!

深入閱讀

有關本教學課程中討論的主題的更多資訊,請參閱以下資源: