ASP.NET MVC中異常Exception攔截的深入理解
一、前言
由于客戶(hù)端的環(huán)境不一致,有可能會(huì)造成我們預(yù)計(jì)不到的異常錯(cuò)誤,所以在項(xiàng)目中,友好的異常信息提示,是非常重要的。在asp.net mvc中實(shí)現(xiàn)異常屬性攔截也非常簡(jiǎn)單,只需要繼承另一個(gè)類(lèi)(System.Web.Mvc.FilterAttribute)和一個(gè)接口(System.Web.Mvc.IExceptionFilter),實(shí)現(xiàn)接口里面OnException方法,或者直接繼承Mvc 提供的類(lèi)System.Web.Mvc.HandleErrorAttribute。
下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧
二、實(shí)現(xiàn)關(guān)鍵邏輯
繼承System.Web.Mvc.HandleErrorAttribute,重寫(xiě)了OnException方法,主要實(shí)現(xiàn)邏輯代碼如下:
public class HandlerErrorAttribute : HandleErrorAttribute
{
/// <summary>
/// 控制器方法中出現(xiàn)異常,會(huì)調(diào)用該方法捕獲異常
/// </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>
/// 寫(xiě)入日志(log4net)
/// </summary>
/// <param name="context">提供使用</param>
private void WriteLog(ExceptionContext context)
{
if (context == null)
return;
if (context.Exception is NoAuthorizeException || context.Exception is UserFriendlyException)
{
//友好錯(cuò)誤提示,未授權(quán)錯(cuò)誤提示,記錄警告日志
LogHelper.Warn(context.Exception.Message);
}
else
{
//異常錯(cuò)誤,
LogHelper.Error(context.Exception);
////TODO :寫(xiě)入錯(cuò)誤日志到數(shù)據(jù)庫(kù)
}
}
}
MVC 過(guò)濾器全局注冊(cè)異常攔截:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandlerErrorAttribute());
}
}
我們看到,context.Exception 分為3種:UserFriendlyException,NoAuthorizeException 或 Exception;UserFriendlyException 是指友好異常,前端友好提示錯(cuò)誤信息。NoAuthorizeException 為401未授權(quán)異常,當(dāng)頁(yè)面未被授權(quán)訪問(wèn)時(shí),返回該異常,并攜帶有未授權(quán)的路徑地址。其他異常統(tǒng)一返回500錯(cuò)誤,并攜帶異常信息。
三、異常處理
1.401 未授權(quán)錯(cuò)誤
異常定義代碼:
/// <summary>
/// 沒(méi)有被授權(quán)的異常
/// </summary>
public class NoAuthorizeException : Exception
{
public NoAuthorizeException(string message)
: base(message)
{
}
}
拋出異常代碼:
throw new NoAuthorizeException("未授權(quán)");
前端UI效果:

2.404 未找到頁(yè)面錯(cuò)誤
MVC的404異常處理,有幾種方式,我們采用了在Global.asax全局請(qǐng)求函數(shù)中處理, 請(qǐng)查看以下代碼
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服務(wù)器內(nèi)部錯(cuò)誤
500異常錯(cuò)誤拋出的異常信息對(duì)象定義:
/// <summary>
/// 異常錯(cuò)誤信息
/// </summary>
[Serializable]
public class ExceptionMessage
{
public ExceptionMessage()
{
}
/// <summary>
/// 構(gòu)造函數(shù)
/// 默認(rèn)顯示異常頁(yè)面
/// </summary>
/// <param name="ex">異常對(duì)象</param>
public ExceptionMessage(Exception ex)
:this(ex, true)
{
}
/// <summary>
/// 構(gòu)造函數(shù)
/// </summary>
/// <param name="ex">異常對(duì)象</param>
/// <param name="isShowException">是否顯示異常頁(yè)面</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>
/// 消息類(lèi)型
/// </summary>
public string MsgType { get; set; }
/// <summary>
/// 消息內(nèi)容
/// </summary>
public string Message { get; set; }
/// <summary>
/// 請(qǐng)求路徑
/// </summary>
public string Path { get; set; }
/// <summary>
/// 程序集名稱(chēng)
/// </summary>
public string Assembly { get; set; }
/// <summary>
/// 異常參數(shù)
/// </summary>
public string ActionArguments { get; set; }
/// <summary>
/// 請(qǐng)求類(lèi)型
/// </summary>
public string HttpMethod { get; set; }
/// <summary>
/// 異常堆棧
/// </summary>
public string StackTrace { get; set; }
/// <summary>
/// 異常源
/// </summary>
public string Source { get; set; }
/// <summary>
/// 服務(wù)器IP 端口
/// </summary>
public string IP { get; set; }
/// <summary>
/// 客戶(hù)端瀏覽器標(biāo)識(shí)
/// </summary>
public string UserAgent { get; set; }
/// <summary>
/// 是否顯示異常界面
/// </summary>
public bool ShowException { get; set; }
/// <summary>
/// 異常發(fā)生時(shí)間
/// </summary>
public string Time { get; set; }
/// <summary>
/// 異常發(fā)生方法
/// </summary>
public string Method { get; set; }
}
拋出異常代碼:
throw new Exception("出錯(cuò)了");
前端UI效果:

4. UserFriendlyException 友好異常
異常定義代碼:
/// <summary>
/// 用戶(hù)友好異常
/// </summary>
public class UserFriendlyException : Exception
{
public UserFriendlyException(string message)
: base(message)
{
}
}
在異常攔截關(guān)鍵代碼中,我們發(fā)現(xiàn)友好異常(UserFriendlyException)其實(shí)是返回了一個(gè)結(jié)果對(duì)象AjaxResult,

AjaxResult對(duì)象的定義:
/// <summary>
/// 表示Ajax操作結(jié)果
/// </summary>
public class AjaxResult
{
/// <summary>
/// 獲取 Ajax操作結(jié)果類(lèi)型
/// </summary>
public ResultType type { get; set; }
/// <summary>
/// 獲取 Ajax操作結(jié)果編碼
/// </summary>
public int errorcode { get; set; }
/// <summary>
/// 獲取 消息內(nèi)容
/// </summary>
public string message { get; set; }
/// <summary>
/// 獲取 返回?cái)?shù)據(jù)
/// </summary>
public object resultdata { get; set; }
}
/// <summary>
/// 表示 ajax 操作結(jié)果類(lèi)型的枚舉
/// </summary>
public enum ResultType
{
/// <summary>
/// 消息結(jié)果類(lèi)型
/// </summary>
info = 0,
/// <summary>
/// 成功結(jié)果類(lèi)型
/// </summary>
success = 1,
/// <summary>
/// 警告結(jié)果類(lèi)型
/// </summary>
warning = 2,
/// <summary>
/// 異常結(jié)果類(lèi)型
/// </summary>
error = 3
}
四、Ajax請(qǐng)求異常時(shí)處理
在異常攔截的關(guān)鍵代碼中,我們有看到,如果是ajax請(qǐng)求時(shí),是執(zhí)行不同的邏輯,這是因?yàn)閍jax的請(qǐng)求,不能直接通過(guò)MVC的路由跳轉(zhuǎn),在請(qǐng)求時(shí)必須返回結(jié)果內(nèi)容

然后在前端ajax的方法中,統(tǒng)一處理返回的錯(cuò)誤,以下是我們項(xiàng)目中用到的ajax封裝,對(duì)異常錯(cuò)誤,進(jìn)行了統(tǒng)一處理。
(function ($) {
"use strict";
$.httpCode = {
success: "1",
fail: "3",
};
// http 通信異常的時(shí)候調(diào)用此方法
$.httpErrorLog = function (msg) {
console.log('=====>' + new Date().getTime() + '<=====');
console.log(msg);
};
// ajax請(qǐng)求錯(cuò)誤處理
$.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請(qǐng)求方法(異步):
* url地址, param參數(shù), callback回調(diào)函數(shù) beforeSend 請(qǐng)求之前回調(diào)函數(shù), complete 請(qǐng)求完成之后回調(diào)函數(shù)
* 考慮到get請(qǐng)求一般將參數(shù)與url拼接一起傳遞,所以將param參數(shù)放置最后
* 返回AjaxResult結(jié)果對(duì)象
*/
$.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請(qǐng)求方法(同步):
* url地址,param參數(shù)
* 返回實(shí)體數(shù)據(jù)對(duì)象
*/
$.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請(qǐng)求方法(異步):
* url地址, param參數(shù), callback回調(diào)函數(shù) beforeSend 請(qǐng)求之前回調(diào)函數(shù), complete 請(qǐng)求完成之后回調(diào)函數(shù)
* 返回AjaxResult結(jié)果對(duì)象
*/
$.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請(qǐng)求方法(同步):
* url地址,param參數(shù), callback回調(diào)函數(shù)
* 返回實(shí)體數(shù)據(jù)對(duì)象
*/
$.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 請(qǐng)求類(lèi)型, url地址, param參數(shù), callback回調(diào)函數(shù)
* 返回實(shí)體數(shù)據(jù)對(duì)象
*/
$.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);
五、總結(jié)
至此,我們發(fā)現(xiàn)其實(shí)MVC的異常處理,真的很簡(jiǎn)單,只需要在過(guò)濾器中全局注冊(cè)之后,然后重寫(xiě)OnException的方法,實(shí)現(xiàn)邏輯即可。關(guān)鍵是在于項(xiàng)目中Ajax請(qǐng)求,需要用統(tǒng)一的封裝方法。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- asp.net?core?MVC?全局過(guò)濾器之ExceptionFilter過(guò)濾器(1)
- Asp.net Mvc 身份驗(yàn)證、異常處理、權(quán)限驗(yàn)證(攔截器)實(shí)現(xiàn)代碼
- 詳解使用Spring MVC統(tǒng)一異常處理實(shí)戰(zhàn)
- springboot springmvc拋出全局異常的解決方法
- ASP.NET MVC異常處理模塊詳解
- MVC異常處理詳解
- ASP.NET MVC下基于異常處理的完整解決方案總結(jié)
- ASP.NET mvc異常處理的方法示例介紹
- 基于SpringMVC的全局異常處理器介紹
- ASP.NET MVC中異常處理&自定義錯(cuò)誤頁(yè)詳析
相關(guān)文章
asp.C#實(shí)現(xiàn)圖片文件與base64string編碼解碼
前些天在opera論壇里面當(dāng)了個(gè)flashblocker腳本文件,顧名思義,就是把網(wǎng)頁(yè)中的flash給過(guò)濾了,過(guò)濾之后呢就會(huì)在原位置顯示一張圖片,以前用firefox時(shí)的flash過(guò)濾插件也是這樣,而且顯示的圖片也一樣,一樣的難看,于是就想換換它。2010-03-03
asp.net MaxLengthValidator 最大長(zhǎng)度驗(yàn)證控件代碼
如果數(shù)據(jù)庫(kù)字段為varchar或char類(lèi)型,ASP.NET控件在可輸入漢字的情況下,MaxLength屬性不能保證在保存到數(shù)據(jù)庫(kù)時(shí)不發(fā)生截?cái)噱e(cuò)誤,因此寫(xiě)了一個(gè)最大長(zhǎng)度驗(yàn)證控件,還可用于多行文本框。2009-12-12
silverlight用webclient大文件上傳的實(shí)例代碼
這篇文章介紹了silverlight用webclient大文件上傳的實(shí)例代碼,有需要的朋友可以參考一下2013-10-10
ASP.NET中GridView 重復(fù)表格列合并的實(shí)現(xiàn)方法
本文通過(guò)GridView 和 Repeater 解決有關(guān)表格顯示數(shù)據(jù)重復(fù)的數(shù)據(jù)列和并的方法,非常實(shí)用,感興趣的朋友一起看下吧2016-08-08
.net core webapi jwt 更為清爽的認(rèn)證詳解
這篇文章主要介紹了.net core webapi jwt 更為清爽的認(rèn)證詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05
ASP.NET 固定標(biāo)題列與欄位的具體實(shí)現(xiàn)
客戶(hù)提這個(gè)要求很久了,最近才時(shí)間弄,但是看到百度中要沒(méi)有很多詳細(xì)的代碼。廢話不多說(shuō)直接貼代碼。2013-06-06
Asp.Net+XML操作基類(lèi)(修改,刪除,新增,創(chuàng)建)
更新內(nèi)容: 1,根據(jù)父節(jié)點(diǎn)屬性讀取字節(jié)點(diǎn)值 2,根據(jù)節(jié)點(diǎn)屬性讀取子節(jié)點(diǎn)值(較省資源模式)2008-07-07

