首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> 關於ASP.NET

構建 ASP.NET MVC4+EF5+EasyUI+Unity2.x 後台管理系統(13):全域例外處理與錯誤頁面友善導向

     編輯:關於ASP.NET

前兩講建立了日誌記錄與例外捕獲機制,但仍有「漏網之魚」——未被 try-catch 捕獲的例外、404 找不到頁面、以及開發者疏忽遺漏的錯誤。本篇實作 Global.asax Application_Error 全域例外處理機制,統一攔截所有未處理例外,記錄詳細錯誤資訊,並友善導向自訂錯誤頁面,避免暴露敏感堆疊追蹤給使用者。

為什麼需要全域例外處理?

  • 安全性:防止堆疊追蹤、資料庫結構、伺服器路徑等敏感資訊洩露
  • 使用者體驗:以友善錯誤頁面替代黃色死機畫面(YSOD)
  • 可觀測性:集中記錄所有未處理例外,便於事後分析與監控告警
  • 程式碼簡化:減少各 Action 中重複的 try-catch 樣板代碼(業務邏輯層仍建議保留)

Application_Error 完整實作

protected void Application_Error(object sender, EventArgs e) {
    var context = HttpContext.Current;
    var requestUrl = context.Request.Url.ToString();
    var server = context.Server;
    
    // 取得最後一個未處理的例外
    Exception lastError = server.GetLastError();
    if (lastError == null) return;
    
    // 1. 記錄例外到日誌系統(資料庫/檔案/第三方如 Elmah、Serilog)
    ExceptionHander.WriteException(lastError);
    
    // 2. 暫存例外供錯誤頁面顯示詳細資訊(開發環境)
    context.Application["LastError"] = lastError;
    
    // 3. 取得 HTTP 狀態碼
    int statusCode = context.Response.StatusCode;
    
    // 4. 導向自訂錯誤頁面
    const string errorHandlerPath = "/SysException/Error";
    
    try {
        if (!string.IsNullOrEmpty(errorHandlerPath)) {
            // 解析虛擬路徑
            string resolvedUrl = new System.Web.UI.Control().ResolveUrl(errorHandlerPath);
            string redirectUrl = string.Format("{0}?ErrorUrl={1}", resolvedUrl, server.UrlEncode(requestUrl));
            
            // 使用 JavaScript 重導向(避免 Response.Redirect 在錯誤處理中拋出 ThreadAbortException)
            string script = string.Format(
                "<script language='javascript' type='text/javascript'>window.top.location='{0}';</script>", 
                redirectUrl
            );
            
            context.Response.Clear();
            context.Response.Write(script);
            context.Response.End();
        }
    } catch (Exception redirectEx) {
        // 導向失敗時的最後防線:直接輸出簡易錯誤訊息
        context.Response.Clear();
        context.Response.Write("系統發生錯誤,請聯繫管理員。");
        context.Response.End();
    }
}

錯誤處理控制器實作

public class SysExceptionController : Controller {
    
    public ActionResult Error(string errorUrl) {
        var lastError = HttpContext.Application["LastError"] as Exception;
        
        var viewModel = new ErrorViewModel {
            ErrorUrl = errorUrl,
            ErrorMessage = lastError?.Message,
            StackTrace = lastError?.StackTrace,
            Source = lastError?.Source,
            TargetSite = lastError?.TargetSite?.ToString(),
            Timestamp = DateTime.Now,
            // 開發環境顯示詳細資訊,正式環境僅顯示友善訊息
            ShowDetails = HttpContext.IsDebuggingEnabled
        };
        
        // 記錄存取錯誤頁面本身的請求
        LogHelper.Info($"錯誤頁面存取: {errorUrl}, 例外: {lastError?.Message}");
        
        Response.StatusCode = 500;
        return View("Error", viewModel);
    }
    
    public ActionResult NotFound() {
        Response.StatusCode = 404;
        return View("NotFound");
    }
}

錯誤頁面 View 範例

@model ErrorViewModel
@{
    ViewBag.Title = "系統錯誤";
    Layout = "~/Views/Shared/_ErrorLayout.cshtml";
}

<div class="error-container">
    <div class="error-icon">⚠</div>
    <h1>系統發生錯誤</h1>
    <p class="error-message">很抱歉,系統在處理您的請求時發生了非預期的錯誤。</p>
    
    @if (Model.ShowDetails) {
        <div class="error-details">
            <h3>詳細資訊(僅開發環境顯示)</h3>
            <p><strong>請求網址:</strong> @Model.ErrorUrl</p>
            <p><strong>錯誤訊息:</strong> @Model.ErrorMessage</p>
            <p><strong>來源:</strong> @Model.Source</p>
            <p><strong>方法:</strong> @Model.TargetSite</p>
            <pre>@Model.StackTrace</pre>
        </div>
    }
    
    <div class="error-actions">
        <a href="javascript:history.back()" class="btn btn-primary">返回上一頁</a>
        <a href="@Url.Action("Index", "Home")" class="btn btn-default">回首頁</a>
        <a href="mailto:support@company.com?subject=系統錯誤回報&body=@Model.ErrorUrl" class="btn btn-link">回報問題</a>
    </div>
</div>

測試驗證

在任意 Action 中植入測試代碼:

public ActionResult TestError() {
    // 模擬未處理例外
    int.Parse("這不是數字"); // FormatException
    return View();
}

瀏覽該頁面將觸發 Application_Error,導向自訂錯誤頁面並記錄完整堆疊追蹤。

錯誤頁面展示

進階增強建議

  1. 整合 Elmah/Serilog:替換自建 ExceptionHander,獲得儀表板、郵件通知、錯誤分組等企業級功能
  2. 區分例外類型:針對 HttpException 404 導向 NotFound 頁面,HttpException 403 導向 AccessDenied 頁面
  3. 例外去重與聚合:相同例外短時間內大量發生時,僅記錄第一次並計數,避免日誌洪水
  4. 健康檢查端點:提供 /health 端點供負載平衡器監控,例外率過高時自動摘除節點
  5. 關聯 ID 追蹤:每個請求生成唯一 CorrelationId,貫穿日誌、錯誤頁面、用戶回報,便於端到端追蹤

web.config 補充配置

<system.web>
    <customErrors mode="RemoteOnly" defaultRedirect="/SysException/Error">
        <error statusCode="404" redirect="/SysException/NotFound" />
        <error statusCode="500" redirect="/SysException/Error" />
    </customErrors>
</system.web>

<system.webServer>
    <httpErrors errorMode="Custom" existingResponse="Replace">
        <remove statusCode="404" />
        <remove statusCode="500" />
        <error statusCode="404" path="/SysException/NotFound" responseMode="ExecuteURL" />
        <error statusCode="500" path="/SysException/Error" responseMode="ExecuteURL" />
    </httpErrors>
</system.webServer>

全域例外處理是系統穩健性的最後一道防線,配合結構化日誌與監控告警,能將「被動修補」轉變為「主動預防」,大幅提升系統可靠性與維運效率。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved