一、前言
由於客戶端的環境不一致,有可能會造成我們預計不到的異常錯誤,所以在項目中,友好的異常信息提示,是非常重要的。在asp.net mvc中實現異常屬性攔截也非常簡單,只需要繼承另一個類(System.Web.Mvc.FilterAttribute)和一個接口(System.Web.Mvc.IExceptionFilter),實現接口里面OnException方法,或者直接繼承Mvc 提供的類System.Web.Mvc.HandleErrorAttribute。
二、實現關鍵邏輯
繼承System.Web.Mvc.HandleErrorAttribute,重寫了OnException方法,主要實現邏輯代碼如下:
-  
          public class HandlerErrorAttribute : HandleErrorAttribute
 -  
          {
 -  
          /// <summary>
 -  
          /// 控制器方法中出現異常,會調用該方法捕獲異常
 -  
          /// </summary>
 -  
          /// <param name="context">提供使用</param>
 -  
          public override void OnException(ExceptionContext context)
 -  
          {
 -  
          WriteLog(context);
 -  
          base.OnException(context);
 -  
          context.ExceptionHandled = true;
 -  
          if (context.Exception is UserFriendlyException)
 -  
          {
 -  
          context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK;
 -  
          context.Result = new ContentResult { Content = new AjaxResult { type = ResultType.error, message = context.Exception.Message }.ToJson() };
 -  
          }
 -  
          else if (context.Exception is NoAuthorizeException)
 -  
          {
 -  
          context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
 -  
          if (!context.HttpContext.Request.IsAjaxRequest())
 -  
          {
 -  
          context.HttpContext.Response.RedirectToRoute("Default", new { controller = "Error", action = "Error401", errorUrl = context.HttpContext.Request.RawUrl });
 -  
          }
 -  
          else
 -  
          {
 -  
          context.Result = new ContentResult { Content = context.HttpContext.Request.RawUrl };
 -  
          }
 -  
          }
 -  
          else
 -  
          {
 -  
          context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
 -  
          ExceptionMessage error = new ExceptionMessage(context.Exception);
 -  
          var s = error.ToJson();
 -  
          if (!context.HttpContext.Request.IsAjaxRequest())
 -  
          {
 -  
          context.HttpContext.Response.RedirectToRoute("Default", new { controller = "Error", action = "Error500", data = WebHelper.UrlEncode(s) });
 -  
          }
 -  
          else
 -  
          {
 -  
          context.Result = new ContentResult { Content = WebHelper.UrlEncode(s) };
 -  
          }
 -  
          }
 -  
          }
 -  
          
 -  
          /// <summary>
 -  
          /// 寫入日志(log4net)
 -  
          /// </summary>
 -  
          /// <param name="context">提供使用</param>
 -  
          private void WriteLog(ExceptionContext context)
 -  
          {
 -  
          if (context == null)
 -  
          return;
 -  
          if (context.Exception is NoAuthorizeException || context.Exception is UserFriendlyException)
 -  
          {
 -  
          //友好錯誤提示,未授權錯誤提示,記錄警告日志
 -  
          LogHelper.Warn(context.Exception.Message);
 -  
          }
 -  
          else
 -  
          {
 -  
          //異常錯誤,
 -  
          LogHelper.Error(context.Exception);
 -  
          
 -  
          ////TODO :寫入錯誤日志到數據庫
 -  
          }
 -  
          }
 -  
          }
 
MVC 過濾器全局注冊異常攔截:
-  
          public class FilterConfig
 -  
          {
 -  
          public static void RegisterGlobalFilters(GlobalFilterCollection filters)
 -  
          {
 -  
          filters.Add(new HandlerErrorAttribute());
 -  
          }
 -  
          }
 
我們看到,context.Exception 分為3種:UserFriendlyException,NoAuthorizeException 或 Exception;UserFriendlyException 是指友好異常,前端友好提示錯誤信息。NoAuthorizeException 為401未授權異常,當頁面未被授權訪問時,返回該異常,並攜帶有未授權的路徑地址。其他異常統一返回500錯誤,並攜帶異常信息。
三、異常處理
1.401 未授權錯誤
異常定義代碼:
-  
          /// <summary>
 -  
          /// 沒有被授權的異常
 -  
          /// </summary>
 -  
          public class NoAuthorizeException : Exception
 -  
          {
 -  
          public NoAuthorizeException(string message)
 -  
          : base(message)
 -  
          {
 -  
          }
 -  
          }
 
拋出異常代碼:
-  
          throw new NoAuthorizeException("未授權");
 
前端UI效果:

2.404 未找到頁面錯誤
MVC的404異常處理,有幾種方式,我們采用了在Global.asax全局請求函數中處理, 請查看以下代碼
-  
          protected void Application_EndRequest()
 -  
          {
 -  
          if (Context.Response.StatusCode == 404)
 -  
          {
 -  
          bool isAjax = new HttpRequestWrapper(Context.Request).IsAjaxRequest();
 -  
          if (isAjax)
 -  
          {
 -  
          Response.Clear();
 -  
          Response.Write(Context.Request.RawUrl);
 -  
          }
 -  
          else
 -  
          {
 -  
          Response.RedirectToRoute("Default", new { controller = "Error", action = "Error404", errorUrl = Context.Request.RawUrl });
 -  
          }
 -  
          }
 -  
          }
 
前端UI效果:

3.500服務器內部錯誤
500異常錯誤拋出的異常信息對象定義:
-  
          /// <summary>
 -  
          /// 異常錯誤信息
 -  
          /// </summary>
 -  
          [Serializable]
 -  
          public class ExceptionMessage
 -  
          {
 -  
          public ExceptionMessage()
 -  
          {
 -  
          }
 -  
          
 -  
          /// <summary>
 -  
          /// 構造函數
 -  
          /// 默認顯示異常頁面
 -  
          /// </summary>
 -  
          /// <param name="ex">異常對象</param>
 -  
          public ExceptionMessage(Exception ex)
 -  
          :this(ex, true)
 -  
          {
 -  
          
 -  
          }
 -  
          /// <summary>
 -  
          /// 構造函數
 -  
          /// </summary>
 -  
          /// <param name="ex">異常對象</param>
 -  
          /// <param name="isShowException">是否顯示異常頁面</param>
 -  
          public ExceptionMessage(Exception ex, bool isShowException)
 -  
          {
 -  
          MsgType = ex.GetType().Name;
 -  
          Message = ex.InnerException != null ? ex.InnerException.Message : ex.Message;
 -  
          StackTrace = ex.StackTrace.Length > 300 ? ex.StackTrace.Substring(0, 300) : 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;
 -  
          
 -  
          ShowException = isShowException;
 -  
          var request = HttpContext.Current.Request;
 -  
          IP = Net.Ip;
 -  
          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>
 -  
          /// 是否顯示異常界面
 -  
          /// </summary>
 -  
          public bool ShowException { get; set; }
 -  
          
 -  
          /// <summary>
 -  
          /// 異常發生時間
 -  
          /// </summary>
 -  
          public string Time { get; set; }
 -  
          
 -  
          /// <summary>
 -  
          /// 異常發生方法
 -  
          /// </summary>
 -  
          public string Method { get; set; }
 -  
          }
 
拋出異常代碼:
-  
          throw new Exception("出錯了");
 
前端UI效果:

4. UserFriendlyException 友好異常
異常定義代碼:
-  
          /// <summary>
 -  
          /// 用戶友好異常
 -  
          /// </summary>
 -  
          public class UserFriendlyException : Exception
 -  
          {
 -  
          public UserFriendlyException(string message)
 -  
          : base(message)
 -  
          {
 -  
          }
 -  
          }
 
在異常攔截關鍵代碼中,我們發現友好異常(UserFriendlyException)其實是返回了一個結果對象AjaxResult,

AjaxResult對象的定義:
-  
          /// <summary>
 -  
          /// 表示Ajax操作結果
 -  
          /// </summary>
 -  
          public class AjaxResult
 -  
          {
 -  
          /// <summary>
 -  
          /// 獲取 Ajax操作結果類型
 -  
          /// </summary>
 -  
          public ResultType type { get; set; }
 -  
          
 -  
          /// <summary>
 -  
          /// 獲取 Ajax操作結果編碼
 -  
          /// </summary>
 -  
          public int errorcode { get; set; }
 -  
          
 -  
          /// <summary>
 -  
          /// 獲取 消息內容
 -  
          /// </summary>
 -  
          public string message { get; set; }
 -  
          
 -  
          /// <summary>
 -  
          /// 獲取 返回數據
 -  
          /// </summary>
 -  
          public object resultdata { get; set; }
 -  
          }
 -  
          /// <summary>
 -  
          /// 表示 ajax 操作結果類型的枚舉
 -  
          /// </summary>
 -  
          public enum ResultType
 -  
          {
 -  
          /// <summary>
 -  
          /// 消息結果類型
 -  
          /// </summary>
 -  
          info = 0,
 -  
          
 -  
          /// <summary>
 -  
          /// 成功結果類型
 -  
          /// </summary>
 -  
          success = 1,
 -  
          
 -  
          /// <summary>
 -  
          /// 警告結果類型
 -  
          /// </summary>
 -  
          warning = 2,
 -  
          
 -  
          /// <summary>
 -  
          /// 異常結果類型
 -  
          /// </summary>
 -  
          error = 3
 -  
          }
 
四、Ajax請求異常時處理
在異常攔截的關鍵代碼中,我們有看到,如果是ajax請求時,是執行不同的邏輯,這是因為ajax的請求,不能直接通過MVC的路由跳轉,在請求時必須返回結果內容


然后在前端ajax的方法中,統一處理返回的錯誤,以下是我們項目中用到的ajax封裝,對異常錯誤,進行了統一處理。
-  
          (function ($) {
 -  
          "use strict";
 -  
          
 -  
          $.httpCode = {
 -  
          success: "1",
 -  
          fail: "3",
 -  
          };
 -  
          // http 通信異常的時候調用此方法
 -  
          $.httpErrorLog = function (msg) {
 -  
          console.log('=====>' + new Date().getTime() + '<=====');
 -  
          console.log(msg);
 -  
          };
 -  
          
 -  
          // ajax請求錯誤處理
 -  
          $.httpError = function (xhr, textStatus, errorThrown) {
 -  
          
 -  
          if (xhr.status == 401) {
 -  
          location.href = "/Error/Error401?errorUrl=" + xhr.responseText;
 -  
          }
 -  
          
 -  
          if (xhr.status == 404) {
 -  
          location.href = "/Error/Error404?errorUrl=" + xhr.responseText;
 -  
          }
 -  
          
 -  
          if (xhr.status == 500) {
 -  
          location.href = "/Error/Error500?data=" + xhr.responseText;
 -  
          }
 -  
          };
 -  
          
 -  
          /* get請求方法(異步):
 -  
          * url地址, param參數, callback回調函數 beforeSend 請求之前回調函數, complete 請求完成之后回調函數
 -  
          * 考慮到get請求一般將參數與url拼接一起傳遞,所以將param參數放置最后
 -  
          * 返回AjaxResult結果對象
 -  
          */
 -  
          $.httpAsyncGet = function (url, callback, beforeSend, complete, param) {
 -  
          $.ajax({
 -  
          url: url,
 -  
          data: param,
 -  
          type: "GET",
 -  
          dataType: "json",
 -  
          async: true,
 -  
          cache: false,
 -  
          success: function (data) {
 -  
          if ($.isFunction(callback)) callback(data);
 -  
          },
 -  
          error: function (XMLHttpRequest, textStatus, errorThrown) {
 -  
          $.httpError(XMLHttpRequest, textStatus, errorThrown);
 -  
          },
 -  
          beforeSend: function () {
 -  
          if (!!beforeSend) beforeSend();
 -  
          },
 -  
          complete: function () {
 -  
          if (!!complete) complete();
 -  
          }
 -  
          });
 -  
          };
 -  
          
 -  
          /* get請求方法(同步):
 -  
          * url地址,param參數
 -  
          * 返回實體數據對象
 -  
          */
 -  
          $.httpGet = function (url, param) {
 -  
          var res = {};
 -  
          $.ajax({
 -  
          url: url,
 -  
          data: param,
 -  
          type: "GET",
 -  
          dataType: "json",
 -  
          async: false,
 -  
          cache: false,
 -  
          success: function (data) {
 -  
          res = data;
 -  
          },
 -  
          error: function (XMLHttpRequest, textStatus, errorThrown) {
 -  
          $.httpError(XMLHttpRequest, textStatus, errorThrown);
 -  
          },
 -  
          });
 -  
          return res;
 -  
          };
 -  
          
 -  
          /* post請求方法(異步):
 -  
          * url地址, param參數, callback回調函數 beforeSend 請求之前回調函數, complete 請求完成之后回調函數
 -  
          * 返回AjaxResult結果對象
 -  
          */
 -  
          $.httpAsyncPost = function (url, param, callback, beforeSend, complete) {
 -  
          $.ajax({
 -  
          url: url,
 -  
          data: param,
 -  
          type: "POST",
 -  
          dataType: "json",
 -  
          async: true,
 -  
          cache: false,
 -  
          success: function (data) {
 -  
          if ($.isFunction(callback)) callback(data);
 -  
          },
 -  
          error: function (XMLHttpRequest, textStatus, errorThrown) {
 -  
          $.httpError(XMLHttpRequest, textStatus, errorThrown);
 -  
          },
 -  
          beforeSend: function () {
 -  
          if (!!beforeSend) beforeSend();
 -  
          },
 -  
          complete: function () {
 -  
          if (!!complete) complete();
 -  
          }
 -  
          });
 -  
          };
 -  
          
 -  
          /* post請求方法(同步):
 -  
          * url地址,param參數, callback回調函數
 -  
          * 返回實體數據對象
 -  
          */
 -  
          $.httpPost = function (url, param, callback) {
 -  
          $.ajax({
 -  
          url: url,
 -  
          data: param,
 -  
          type: "POST",
 -  
          dataType: "json",
 -  
          async: false,
 -  
          cache: false,
 -  
          success: function (data) {
 -  
          if ($.isFunction(callback)) callback(data);
 -  
          },
 -  
          error: function (XMLHttpRequest, textStatus, errorThrown) {
 -  
          $.httpError(XMLHttpRequest, textStatus, errorThrown);
 -  
          },
 -  
          });
 -  
          },
 -  
          
 -  
          /* ajax異步封裝:
 -  
          * type 請求類型, url地址, param參數, callback回調函數
 -  
          * 返回實體數據對象
 -  
          */
 -  
          $.httpAsync = function (type, url, param, callback) {
 -  
          $.ajax({
 -  
          url: url,
 -  
          data: param,
 -  
          type: type,
 -  
          dataType: "json",
 -  
          async: true,
 -  
          cache: false,
 -  
          success: function (data) {
 -  
          if ($.isFunction(callback)) callback(data);
 -  
          },
 -  
          error: function (XMLHttpRequest, textStatus, errorThrown) {
 -  
          $.httpError(XMLHttpRequest, textStatus, errorThrown);
 -  
          },
 -  
          });
 -  
          };
 -  
          })(jQuery);
 
五、總結
至此,我們發現其實MVC的異常處理,真的很簡單,只需要在過濾器中全局注冊之后,然后重寫OnException的方法,實現邏輯即可。關鍵是在於項目中Ajax請求,需要用統一的封裝方法。
