前言:最近在某个项目里面遇到一个有点纠结的小问题,通过半天时间的思索和尝试,问题获得解决。在此记录一下解决的过程,以及解决问题的过程当中对.net里面MVC异常处理的思考。都是些老生常谈的问题,很少说,直接上“主菜”。javascript
本文原创地址:http://www.cnblogs.com/landeanfen/p/8135844.htmlhtml
项目是一个传统.net framework的MVC项目,为了简便,项目里面定义了一个自定义异常类用于向客户端传递错误消息,客户端接收到异常的消息时在浏览器里面弹出提示。先来看看这个自定义异常类CustormerException的定义java
public class CustomerException : System.Exception { public CustomerException() { } public CustomerException(string message) : base(message) { } public CustomerException(string message, params object[] args) : base(string.Format(message, args)) { } }
为了模拟重现问题,我尽可能将代码简化再简化。web
[BaseException] public class DefaultController : Controller { // GET: Default public ActionResult Index() { return View(); } public JsonResult Login(string userName, string password) { if (userName == "admin" && password == "admin") { return Json(true, JsonRequestBehavior.AllowGet); } else { throw new CustomerException("用户名或者密码错误"); } } } public class BaseExceptionAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception is CustomerException) { filterContext.ExceptionHandled = true; filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest; var result = new ContentResult() { Content = filterContext.Exception.Message, ContentType = MediaTypeNames.Text.Plain }; filterContext.Result = result; } else { //记录日志 } base.OnException(filterContext); } }
代码不复杂,就是一个通用的异常过滤器,用于记录日志和传递消息到客户端。ajax
而后咱们看看客户端的测试代码:浏览器
@{ ViewBag.Title = "Index"; } 用户名:<input type="text" id="username"/> 密码 :<input type="text" id="password"/> <button id="btnAjaxError" type="button">登录</button> @section Script { <script type="text/javascript"> $(function () { $("#btnAjaxError").click(function () { $.ajax({ url:"/Default/Login", data: { userName: $('#username').val(), password: $('#password').val() }, type: 'post' }).done(function (data) { console.log("successful:"+data); }).fail(function (a, b, c) { debugger; console.log("fail:"+a.responseText); }); }); }); </script> }
本地调试、运行获得正常结果ide
发布到IIS,本地访问仍然正常。但是当咱们远程访问的时候问题出现了。post
全部的远程访问机器上面都出现了系统默认的错误消息,而不是咱们返回的业务异常消息。测试
对于这种本地能看到详细异常,而远程看不到详细异常的问题,相信有必定经验的朋友确定想到了一个配置,那就是Web.config里面的CustomErrors节点,咱们配置下默认开启自定义异常不就好了吗。嘿嘿!就是这么简单!博主当初也是这么乐呵呵的去尝试的。咱们在Web.config的System.web节点下面加入这个节点url
<customErrors mode="On"></customErrors>
但是很遗憾,问题依旧!后来想是否是本身对于On、Off、RemoteOnly的理解有误?因而乎三个项逐个尝试,结果均已失败了结!
因而乎开始有点郁闷了,这种问题原来怎么没遇到过了,代码“貌似”没什么大问题啊,若是有问题,本地应该也不能获得才对啊。因而乎分析,这可能不是咱们代码的问题,而是IIS给我作了一层统一的异常处理,咱们只须要将这层统一的异常处理去掉就好了啊。道理是这么个道理,但是如何去实现呢。因而乎把IIS的各个功能都试了个遍,最后的谷歌的一篇帖子里面找到了一些帮助。解决方案以下。
上文说到这个问题或许不是代码的问题,而是IIS配置的问题。因而乎真的让博主找到了解决方案。解决步骤以下:
原来,IIS默认是不让远程用户查看异常的详细错误的,若是是远程用户,IIS会默认给你返回一个各类状态码对应的默认消息,咱们自定义的消息将会被此覆盖。若是改为选中第二项,就表示不论是本地用户仍是远程用户都可以看到详细异常。
这样配置以后不用更改任何代码,不用理会是否配置了CustomErrors节点,远程用户都可以正常获取到程序返回的异常消息:
有了上面的解决方案,为什么还会有“是代码的问题”的解决方案呢?这才是本文想要表达的中心思想。既然咱们经过配置IIS的错误页能够解决这个问题,那么咱们为何不能在程序的范畴内去解决呢?博主是一个有点喜欢刨根问题的人,不断分析代码后发现,既然系统的默认错误消息能够覆盖咱们的自定义异常消息,那么反过来,咱们自定义的异常消息为何就不能覆盖系统默认的异常消息呢?因而乎发如今重写父类的OnException方法的时候,上面的代码咱们是先执行的咱们自定义的异常消息,而后再调用 base.OnException(filterContext); 去执行系统默认的异常消息处理的,那么咱们将这个顺序倒置一下,反过来是否是可行呢?因而代码就变成了这样:
public class BaseExceptionAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { base.OnException(filterContext); if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception is CustomerException) { filterContext.ExceptionHandled = true; filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest; var result = new ContentResult() { Content = filterContext.Exception.Message, ContentType = MediaTypeNames.Text.Plain }; filterContext.Result = result; } else { //记录日志 } } }
咱们将上面经过配置IIS错误页的解决方案还原,改为默认的配置。去掉CustomErrors节点,从新发布以后,问题完美解决:
问题能解决,说明博主上面的推想或许是正确的,自定义异常和默认异常是存在一个前后顺序的,咱们若是要覆盖系统的异常,须要将咱们自定义异常的代码放在后面执行。这个论断是经过上述解决问题的思路推理得来的,并不必定正确,有兴趣的能够反编译下dll看下是否真是这样!
颇有趣的一点就是,这样改了代码以后,咱们若是在web.config里面加入customErrors节点,而且将mode设置为Off,远程访问的时候获得的异常消息又变成了“错误的请求”。其实这不难理解,当你禁用自定义错误信息,那么系统确定会给你返回默认的异常信息了。
由上述的两种解决方案能够看出这里其实有三道防线:
第一道防线是最外层的防线,就是IIS的错误页配置,若是这层配置选择的是详细错误,那么无论你其余的配置是什么样,都会返回用户自定义的错误信息;
第二道防线是中间的那层,就是web.config里面的CustomErrors节点,若是第一道防线是默认配置,这层防线才会生效;
第三道防线才是代码的范畴,这个受限于CustomErrors节点的配置。
本文原创出处:http://www.cnblogs.com/landeanfen/
欢迎各位转载,可是未经做者本人赞成,转载文章以后必须在文章页面明显位置给出做者和原文链接,不然保留追究法律责任的权利