mvc自定義全局異常處理


  異常信息處理是任何網站必不可少的一個環節,怎么有效顯示,記錄,傳遞異常信息又成為重中之重的問題。本篇將基於上篇介紹的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管理平台進行升級,主要實現管理界面方便查看當前運行的所有任務和管理任務。講解管理平台運用到的技術,敬請期待!

 

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com