ASP.NET MVC 异常Exception拦截
一、前言
由于客户端的环境不一致,有可能会造成我们预计不到的异常错误,所以在项目中,友好的异常信息提示,是非常重要的。在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请求,需要用统一的封装方法。