前言:最近在某个项目里面遇到一个有点纠结的小问题,经过半天时间的思索和尝试,问题得到解决。在此记录一下解决的过程,以及解决问题的过程中对.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的各个功能都试了个遍,最后的谷歌的一篇帖子里面找到了一些帮助。解决方案如下。

三、解决方案

1、“不是代码的问题”的解决方案

上文说到这个问题或许不是代码的问题,而是IIS配置的问题。于是乎真的让博主找到了解决方案。解决步骤如下:

原来,IIS默认是不让远程用户查看异常的详细错误的,如果是远程用户,IIS会默认给你返回一个各种状态码对应的默认消息,我们自定义的消息将会被此覆盖。如果改成选中第二项,就表示不管是本地用户还是远程用户均可以看到详细异常。

这样配置之后不用更改任何代码,不用理会是否配置了CustomErrors节点,远程用户均可以正常获取到程序返回的异常消息:

2、“是代码的问题”的解决方案

有了上面的解决方案,为何还会有“是代码的问题”的解决方案呢?这才是本文想要表达的中心思想。既然我们通过配置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/

欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利

MVC系列——一个异常消息传递引发的思考的更多相关文章

  1. 一个python问题引发的思考

    问题: pyqt5下开发的时候,遇到了一个这样的问题.Traceback (most recent call last):File “test.py”, line 3, in from PyQt5.Q ...

  2. 一次线上Redis类转换异常排查引发的思考

    之前同事反馈说线上遇到Redis反序列化异常问题,异常如下: XxxClass1 cannot be cast to XxxClass2 已知信息如下: 该异常不是必现的,偶尔才会出现: 出现该异常后 ...

  3. 由项目中一个hash2int函数引发的思考

    hash2int /** * 计算一个字符串的md5折算成int返回 * @param type $str * @return type */ function hash2int($str) { $m ...

  4. 一个小BUG引发的思考。(论开发与测试之间的那点事)

    标题不是“一个馒头引发的血案”. 言归正传:今天上午测试的时候,发现了一个BUG,如图: 一个用肉眼就能发现的BUG.原因当然是因为开发同事没有自测试,流入到了测试人员这里了. 无非是开发同事不严谨造 ...

  5. 一个校验接口引发的思考--我真的了解Response吗

    一个校验接口 最近,我需要对接一个外部接口,基本功能是:校验指定的门店是否完善了货运信息.接口大致是这样的: POST https://******/Dealer/CheckCarrier Heads ...

  6. 由一个DAOHelper类引发的思考

    这是一篇发牢骚的文章,可以这么说吧.DAOHelper究竟有什么用呢?用我自己的话去理解,DAOHelper的存在正是敏捷开发的产物,即快速开发. 我们究竟能从项目中学到什么呢?有的人可能会说,从一个 ...

  7. 一个JAVA题引发的思考

    转载自:http://www.cnblogs.com/heshan664754022/archive/2013/03/24/2979495.html 十年半山 今天在论坛闲逛的时候发现了一个很有趣的题 ...

  8. IK分词器实现原理剖析 —— 一个小问题引发的思考

    前言: 网上很多的文章都建议在使用IK分词器的时候,建立索引的时候使用ik_max_word模式:搜索的时候使用ik_smart模式.理由是max_word模式分词的结果会包含smart分词的结果,这 ...

  9. Spring之LoadTimeWeaver——一个需求引发的思考---转

    原文地址:http://www.myexception.cn/software-architecture-design/602651.html Spring之LoadTimeWeaver——一个需求引 ...

随机推荐

  1. 73、django之setting配置汇总

    前面的随笔中我们经常会改setting配置也经常将一些配置混淆今天主要是将一些常见的配置做一个汇总. setting配置汇总 1.app路径 INSTALLED_APPS = [ 'django.co ...

  2. Android音视频通话过程中最小化成悬浮框的实现(类似Android8.0画中画效果)

    关于音视频通话过程中最小化成悬浮框这个功能的实现,网络上类似的文章很多,但是好像还没看到解释的较为清晰的,这里因为项目需要实现了这样的一个功能,今天我把它记录下来,一方面为了以后用到便于自己查阅,一方 ...

  3. C#设计模式之十六迭代器模式(Iterator Pattern)【行为型】

    一.引言   今天我们开始讲"行为型"设计模式的第三个模式,该模式是[迭代器模式],英文名称是:Iterator Pattern.还是老套路,先从名字上来看看."迭代器模 ...

  4. python实战===如何优雅的打飞机

    这是一个打飞机的游戏,结构如下: 其中images中包含的素材为 命名为alien.png    命名为ship.png 游戏效果运行是这样的: 敌军,也就是体型稍微大点的,在上方左右移动,并且有规律 ...

  5. head first python菜鸟学习笔记(第四章)

    1,p124,错误:NameError: name 'print_lol' is not defined 要想文件内如图显示,需要把调用BIF print()改为调用第二章的nester模块中的pri ...

  6. 算法帖——用舞蹈链算法(Dancing Links)求解俄罗斯方块覆盖问题

    问题的提出:如下图,用13块俄罗斯方块覆盖8*8的正方形.如何用计算机求解? 解决这类问题的方法不一而足,然而核心思想都是穷举法,不同的方法仅仅是对穷举法进行了优化 用13块不同形状的俄罗斯方块(每个 ...

  7. Struts 2 入门

    Struts 2  入门: 一:Struts 2执行流程: 1 客户端发送请求: 2这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤 ...

  8. 基于 HTML5 Canvas 的 3D 模型列表贴图

    少量图片对于我们赋值是没有什么难度,但是如果图片的量大的话,我们肯定希望能很直接地显示在界面上供我们使用,再就是排放的位置等等,这些都需要比较直观的操作,在实际应用中会让我们省很多力以及时间.下面这个 ...

  9. Spring集成Redis缓存

    作者:13 GItHub:https://github.com/ZHENFENG13 版权声明:本文为原创文章,未经允许不得转载. 整合Redis 本来以为类似的Redis教程和整合代码应该会很多,因 ...

  10. npm常用命令整理

    npm是一个NodeJS包管理跟分发工具,已经成为了非官方的发布node模块(包)的标准.它可以帮助我们解决代码部署上的一些问题,将开发者从繁琐的包管理工作中(版本.依赖等)解放出来,更加专注于功能上 ...