前言:最近在某个项目里面遇到一个有点纠结的小问题,经过半天时间的思索和尝试,问题得到解决。在此记录一下解决的过程,以及解决问题的过程中对. net 里面 MVC 异常处理的思考。都是些老生常谈的问题,不多说,直接上 "主菜"。
本文原创地址: http://www.cnblogs.com/landeanfen/p/8135844.html
项目是一个传统. net framework 的 MVC 项目,为了简便,项目里面定义了一个自定义异常类用于向客户端传递错误消息,客户端接收到异常的消息时在浏览器里面弹出提示。先来看看这个自定义异常类 CustormerException 的定义
- 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))
- {
- }
- }
为了模拟重现问题,我尽量将代码简化再简化。
- [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);
- }
- }
代码不复杂,就是一个通用的异常过滤器,用于记录日志和传递消息到客户端。
然后我们看看客户端的测试代码:
- @ {
- 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>
- }"
本地调试、运行得到正常结果
发布到 IIS,本地访问仍然正常。可是当我们远程访问的时候问题出现了。
所有的远程访问机器上面都出现了系统默认的错误消息,而不是我们返回的业务异常消息。
对于这种本地能看到详细异常,而远程看不到详细异常的问题,相信有一定经验的朋友肯定想到了一个配置,那就是 web.config 里面的 CustomErrors 节点,我们配置下默认开启自定义异常不就行了吗。嘿嘿!就是这么简单!博主当初也是这么乐呵呵的去尝试的。我们在 Web.config 的 System.web 节点下面加入这个节点
- <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/
欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利
来源: https://www.cnblogs.com/landeanfen/p/8135844.html