mvc自定義全局異常處理

来源:http://www.cnblogs.com/yanweidie/archive/2016/03/10/5229028.html
-Advertisement-
Play Games

異常信息處理是任何網站必不可少的一個環節,怎麼有效顯示,記錄,傳遞異常信息又成為重中之重的問題。本篇將基於上篇介紹的html2cancas截圖功能,實現mvc自定義全局異常處理。先看一下最終實現效果:http://yanweidie.myscloud.cn/Home/Index 閱讀目錄 我理解中好


  異常信息處理是任何網站必不可少的一個環節,怎麼有效顯示,記錄,傳遞異常信息又成為重中之重的問題。本篇將基於上篇介紹的html2cancas截圖功能,實現mvc自定義全局異常處理。先看一下最終實現效果:http://yanweidie.myscloud.cn/Home/Index

閱讀目錄

回到頂部

我理解中好的異常處理

    好的異常信息處理應該具有以下幾個優點

  • 顯示效果佳,而不是原生黃頁
  • 能夠從異常中直接分析出異常源
  • 能夠記錄傳遞異常信息給開發人員

     1.第一點顯示效果方面可以自定義頁面,常見的包括404和500狀態碼頁面。在mvc中404頁面可以通過以下兩種方式進行自定義

  
<system.web>
<!--添加customErrors節點 定義404跳轉頁面-->
 <customErrors mode="On">
      <error statusCode="404" redirect="/Error/Path404" />
    </customErrors>
 </system.web>
//Global文件的EndRequest監聽Response狀態碼
protected void Application_EndRequest()
{
  var statusCode = Context.Response.StatusCode;
    var routingData = Context.Request.RequestContext.RouteData;
    if (statusCode == 404 || statusCode == 500)
    {
      Response.Clear();
       Response.RedirectToRoute("Default", new { controller = "Error", action = "Path404" });
    }
}     
      2.第二點 異常信息應該詳細,能夠記錄下請求參數,請求地址,瀏覽器版本伺服器和當前用戶等相關信息,這就需要對異常信息記錄改造加工       3.第三點 常見的異常信息都是記錄在日誌文件裡面,日誌文件過大時也不太好分析。發生異常時要是能馬上將異常信息通過郵件或者圖片等方式發給開發者,可以加快分析速度。 回到頂部

自定義異常處理

    

  這裡採用mvc的過濾器進行異常處理,分別為介面500錯誤和頁面500錯誤進行處理,介面部分異常需要記錄請求參數,方便分析異常。

     首先定義了異常信息實體,異常實體包含了 請求地址類型(頁面,介面),伺服器相關信息(位數,CPU,操作系統,iis版本),客戶端信息(UserAgent,HttpMethod,IP)

     異常實體代碼如下

    /// <summary>
    /// 系統錯誤信息
    /// </summary>
    public class ErrorMessage
    {
        public ErrorMessage()
        {

        }
        public ErrorMessage(Exception ex,string type)
        {
            MsgType = ex.GetType().Name;
            Message = ex.InnerException != null ? ex.InnerException.Message : ex.Message;
            StackTrace = ex.StackTrace;
            Source = ex.Source;
            Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            Assembly = ex.TargetSite.Module.Assembly.FullName;
            Method = ex.TargetSite.Name;
            Type = type;

            DotNetVersion = Environment.Version.Major + "." + Environment.Version.Minor + "." + Environment.Version.Build + "." + Environment.Version.Revision;
            DotNetBit = (Environment.Is64BitProcess ? "64" : "32") + "";
            OSVersion = Environment.OSVersion.ToString();
            CPUCount = Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS");
            CPUType = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER");
            OSBit = (Environment.Is64BitOperatingSystem ? "64" : "32") + "";

            var request = HttpContext.Current.Request;
            IP = GetIpAddr(request) + ":" + request.Url.Port;
            IISVersion = request.ServerVariables["SERVER_SOFTWARE"];
            UserAgent = request.UserAgent;
            Path = request.Path;
            HttpMethod = request.HttpMethod;
        }
        /// <summary>
        /// 消息類型
        /// </summary>
        public string MsgType { get; set; }

        /// <summary>
        /// 消息內容
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        /// 請求路徑
        /// </summary>
        public string Path { get; set; }

        /// <summary>
        /// 程式集名稱
        /// </summary>
        public string Assembly { get; set; }

        /// <summary>
        /// 異常參數
        /// </summary>
        public string ActionArguments { get; set; }

        /// <summary>
        /// 請求類型
        /// </summary>
        public string HttpMethod { get; set; }

        /// <summary>
        /// 異常堆棧
        /// </summary>
        public string StackTrace { get; set; }

        /// <summary>
        /// 異常源
        /// </summary>
        public string Source { get; set; }

        /// <summary>
        /// 伺服器IP 埠
        /// </summary>
        public string IP { get; set; }

        /// <summary>
        /// 客戶端瀏覽器標識
        /// </summary>
        public string UserAgent { get; set; }

        /// <summary>
        /// .NET解釋引擎版本
        /// </summary>
        public string DotNetVersion { get; set; }

        /// <summary>
        ///  應用程式池位數
        /// </summary>
        public string DotNetBit { get; set; }


        /// <summary>
        /// 操作系統類型
        /// </summary>
        public string OSVersion { get; set; }

        /// <summary>
        /// 操作系統位數
        /// </summary>
        public string OSBit { get; set; }

        /// <summary>
        /// CPU個數
        /// </summary>
        public string CPUCount { get; set; }

        /// <summary>
        /// CPU類型
        /// </summary>
        public string CPUType { get; set; }

        /// <summary>
        /// IIS版本
        /// </summary>
        public string IISVersion { get; set; }

        /// <summary>
        /// 請求地址類型
        /// </summary>
        public string Type { get; set; }

        /// <summary>
        /// 是否顯示異常界面
        /// </summary>
        public bool ShowException { get; set; }

        /// <summary>
        /// 異常發生時間
        /// </summary>
        public string Time { get; set; }

        /// <summary>
        /// 異常發生方法
        /// </summary>
        public string Method { get; set; }

        //這段代碼用戶請求真實IP
        private static string GetIpAddr(HttpRequest request)
        {
            //HTTP_X_FORWARDED_FOR
            string ipAddress = request.ServerVariables["x-forwarded-for"];
            if (!IsEffectiveIP(ipAddress))
            {
                ipAddress = request.ServerVariables["Proxy-Client-IP"];
            }
            if (!IsEffectiveIP(ipAddress))
            {
                ipAddress = request.ServerVariables["WL-Proxy-Client-IP"];
            }
            if (!IsEffectiveIP(ipAddress))
            {
                ipAddress = request.ServerVariables["Remote_Addr"];
                if (ipAddress.Equals("127.0.0.1") || ipAddress.Equals("::1"))
                {
                    // 根據網卡取本機配置的IP
                    IPAddress[] AddressList = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
                    foreach (IPAddress _IPAddress in AddressList)
                    {
                        if (_IPAddress.AddressFamily.ToString() == "InterNetwork")
                        {
                            ipAddress = _IPAddress.ToString();
                            break;
                        }
                    }
                }
            }
            // 對於通過多個代理的情況,第一個IP為客戶端真實IP,多個IP按照','分割
            if (ipAddress != null && ipAddress.Length > 15)
            {
                if (ipAddress.IndexOf(",") > 0)
                {
                    ipAddress = ipAddress.Substring(0, ipAddress.IndexOf(","));
                }
            }
            return ipAddress;
        }

        /// <summary>
        /// 是否有效IP地址
        /// </summary>
        /// <param name="ipAddress">IP地址</param>
        /// <returns>bool</returns>
        private static bool IsEffectiveIP(string ipAddress)
        {
            return !(string.IsNullOrEmpty(ipAddress) || "unknown".Equals(ipAddress, StringComparison.OrdinalIgnoreCase));
        }
    }

上面代碼中用到了獲取客戶端請求IP的方法,用於獲取請求來源的真實IP。

基礎異常信息定義完後,剩下的是異常記錄和頁面跳轉了,mvc中的異常過濾器實現如下。

 /// <summary>
    /// 全局頁面控制器異常記錄
    /// </summary>
    public class CustomErrorAttribute : HandleErrorAttribute
    {
        public override void OnException(ExceptionContext filterContext)
        {
            base.OnException(filterContext);

            ErrorMessage msg = new ErrorMessage(filterContext.Exception, "頁面");
            msg.ShowException = MvcException.IsExceptionEnabled();

            //錯誤記錄
            LogHelper.WriteLog(JsonConvert.SerializeObject(msg, Formatting.Indented), null);

            //設置為true阻止golbal裡面的錯誤執行
            filterContext.ExceptionHandled = true;
            filterContext.Result = new ViewResult() { ViewName = "/Views/Error/ISE.cshtml", ViewData = new ViewDataDictionary<ErrorMessage>(msg) };

        }
    }

    /// <summary>
    /// 全局API異常記錄
    /// </summary>
    public class ApiHandleErrorAttribute : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext filterContext)
        {
            base.OnException(filterContext);

            //異常信息
            ErrorMessage msg = new ErrorMessage(filterContext.Exception, "介面");
            //介面調用參數
            msg.ActionArguments = JsonConvert.SerializeObject(filterContext.ActionContext.ActionArguments, Formatting.Indented);
            msg.ShowException = MvcException.IsExceptionEnabled();

            //錯誤記錄
            string exMsg = JsonConvert.SerializeObject(msg, Formatting.Indented);
            LogHelper.WriteLog(exMsg, null);

            filterContext.Response = new HttpResponseMessage() { StatusCode = HttpStatusCode.InternalServerError, Content = new StringContent(exMsg) };
        }
    }

    /// <summary>
    /// 異常信息顯示
    /// </summary>
    public class MvcException
    {
        /// <summary>
        /// 是否已經獲取的允許顯示異常
        /// </summary>
        private static bool HasGetExceptionEnabled = false;

        private static bool isExceptionEnabled;

        /// <summary>
        /// 是否顯示異常信息
        /// </summary>
        /// <returns>是否顯示異常信息</returns>
        public static bool IsExceptionEnabled()
        {
            if (!HasGetExceptionEnabled)
            {
                isExceptionEnabled = GetExceptionEnabled();
                HasGetExceptionEnabled = true;
            }
            return isExceptionEnabled;
        }

        /// <summary>
        /// 根據Web.config AppSettings節點下的ExceptionEnabled值來決定是否顯示異常信息
        /// </summary>
        /// <returns></returns>
        private static bool GetExceptionEnabled()
        {
            bool result;
            if(!Boolean.TryParse(ConfigurationManager.AppSettings["ExceptionEnabled"],out result))
            {
                return false;
            }
            return result;
        }
    }

值得註意的是上面的MvcException類的GetExceptionEnabled方法,該方法從web.config appsetting中讀取節點"ExceptionEnabled"來控制異常信息是否初始化顯示。異常信息除了顯示在頁面,還使用了log4net組件記錄在錯誤日誌中,方便留痕。

過濾器定義完成後,需要在filterconfig添加引用

    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new CustomErrorAttribute());
            filters.Add(new HandleErrorAttribute());
        }
    }

 

回到頂部

問題拓展

  後臺異常處理代碼完成以後,前臺還需進行相應的處理。這裡主要針對api介面,因為請求頁面後臺可以直接轉向500錯誤頁面,而api介面一般是通過ajax或者客戶端httpclient請求的,如果錯誤了跳轉到500頁面,這樣對客戶端來說就不友好了。基於這點所以api請求異常返回了異常的詳細json對象,讓客戶端自己進行異常處理。我這裡給出ajax處理異常的方式。

     在jquery中全局ajax請求可以設置相應預設參數,比如下麵代碼設置了全局ajax請求為非同步請求,不緩存

//ajax請求全局設置
$.ajaxSetup({
    //非同步請求
    async: true,
    //緩存設置
    cache: false
});

    ajax請求完成會觸發Complete事件,在jquery中全局Complete事件可以通過下麵代碼監聽

$(document).ajaxComplete(function (evt, request, settings) {
    var text = request.responseText;
    if (text) {
        try {
            //Unauthorized  登錄超時或者無許可權
            if (request.status == "401") {
                var json = $.parseJSON(text);
                if (json.Message == "logout") {
                    //登錄超時,彈出系統登錄框
                } else {
                    layer.alert(json.ExceptionMessage ? json.ExceptionMessage : "系統異常,請聯繫系統管理員", {
                        title: "錯誤提醒",
                        icon: 2
                    });
                }
            } else if (request.status == "500") {
                var json = $.parseJSON(text);
                $.ajax({
                    type: "post",
                    url: "/Error/Path500",
                    data: { "": json },
                    data: json,
                    dataType: "html",
                    success: function (data) {
                        //頁面層
                        layer.open({
                            title: '異常信息',
                            type: 1,
                            shade: 0.8,
                            shift: -1,
                            area: ['100%', '100%'],
                            content: data,
                        });
                    }
                });

            }
        } catch (e) {
            console.log(e);
        }
    }
});
紅色部分代碼就是我用來處理500錯誤的代碼,重新發請求到異常顯示界面渲染成html後顯示。其實這麼做無疑增加了一次請求,最好的實現方式,直接通過異常信息json,通過js繪製出html。至此完成了mvc全局的頁面,介面異常信息處理。通過結合上面的前端截圖插件,快速截圖留證,方便後續程式員分析異常信息。   回到頂部

總結

  通過一點小小的改造,我們完成了一個既美觀又方便拓展的錯誤處理方式。看到上面萌萌的圖片你是否心動了,想馬上下載代碼體驗一把呢。下麵就給出本文所有的源代碼:

     svn代碼瀏覽:http://code.taobao.org/p/MyCustomGlobalError/src/trunk         svn工具代碼checkout地址:http://code.taobao.org/svn/MyCustomGlobalErro

      預告一下,下一篇將會對之前的TaskManager管理平臺進行升級,主要實現管理界面方便查看當前運行的所有任務和管理任務。講解管理平臺運用到的技術,敬請期待!

 

 

如果,您認為閱讀這篇博客讓您有些收穫,不妨點擊一下右下角的推薦按鈕。
如果,您希望更容易地發現我的新博客,不妨點擊一下綠色通道的關註我
因為,我的寫作熱情也離不開您的肯定支持。

感謝您的閱讀,如果您對我的博客所講述的內容有興趣,請繼續關註我的後續博客,我是焰尾迭 。


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

-Advertisement-
Play Games
更多相關文章
  • 根據大牛的方案修改的自己使用的Actor!
  • 關於C# webform 項目發佈 註意:aspx頁面無任何代碼,每個頁面都以dll形式發佈帶bin文件夾里 具體操作方法如下圖:
  • Line 在兩個坐標點之間畫一條直線,通過四個屬性設置它的起始和結束 <Line Stroke="Blue" StrokeThickness="3" X1="20" Y1="20" X2="300" Y2="20"></Line> 如果線條是畫在Canvas畫布中,那麼Canvas的附加屬性Top和
  • 經過上面的折騰,我成功用批處理編譯 .NET Framework 4.5.2 項目後, 我並未滿足, 我想要更方便的, 無須安裝那麼多操蛋的東西, 只需要有運行時環境就可以了, 行不行? 答案當然是可以的, 那便是近年漸火的 roslyn 開源項目.
  • 程式效果 最終得到程式的運行效果如圖。拖動Slider可以使按鈕的背景色出現相應變化。 需求分析和架構設計 如果是你,接到了這樣的一個程式設計要求,會怎樣思考?第一步當然是需求分析啦。這個程式相對簡單,需要分析的主要是各個控制項之間的數據聯繫。這主要體現在Slider, Textbox和Button間
  • 編寫WinForm程式時,都會碰到一個問題。就是WinForm視窗在不同解析度下的大小問題。舉例說明,你編寫的WinForm視窗在1024×768下是合適、勻稱的。不過,如果用戶的電腦的解析度為1400×900時,你的WinForm視窗就顯得偏小,其中的字體和控制項都顯得偏小。如果用戶的解析度為64
  • 用vs2010開發項目鏈接伺服器時出現 之前是因為許可權問題,每次打開項目文件時 ,都要先直接進伺服器然後打開項目。 用了兩天,又不行了。就這個問題已經問了了老大不下3次了。 其實很是不想再麻煩他老人家的啦,在網上找的解決方案說是這樣設置 菜單欄==》調試==》選項和設置==》源代碼管理==》當前源代
  • 最近需要用到WCF,所以對WCF進行瞭解。在實踐中學習新知識是最快的,接下來先做了一個簡單的WCF服用應用示例。 本文的WCF服務應用功能很簡單,卻涵蓋了一個完整WCF應用的基本結構。希望本文能對那些準備開始學習WCF的初學者提供一些幫助。 在這個例子中,我將實現一個簡單的書籍數據查詢功能(Book...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...