[转载]在MVC2.0 中 遭遇无法被 Try Catch 的 “Exception” – RyanDing – 博客园.
前天当我为新项目新增完日志模块后对日志模块进行测试,测试时居然发现开发人员一段非常简单的代码,而且很标准的try … catch .. 写法。代码如整理如下:
1 public JsonResult SaveTest() 2 { 3 try 4 { 5 //LinqToSql:返回IQueryable数据集合。 6 var iQueryableData = (from o in _Context.Orders//.Where(o => o.OrderID == 10248) 7 select new 8 { 9 ShipName = o.ShipName, 10 Employee = o.Employee, 11 }).ToList(); 12 13 //LINQ:返回IEnumerable集合。 14 var iEnumerableData = from d in iQueryableData 15 select new 16 { 17 ShipName = d.ShipName, 18 EmployeeName = d.Employee.LastName //空引用未处理引发程序异常。 19 }; 20 21 return Json(new { Success = true, Msg = iEnumerableData }, JsonRequestBehavior.AllowGet); 22 } 23 catch (Exception ex) 24 { 25 return Json(new { Success = false, Msg = ex.Message }, JsonRequestBehavior.AllowGet); 26 } 27 }
为方便大家阅读,我用 NORTHWIND 数据库。同时在该数据库内执行 SQL :update orders set EmployeeID =null where OrderID =10248 。这样造成上述代码第18 行Linq代码迭代时产生异常。您认为 这个异常可以被catch住么?答案当然是否定的!
当发现这个Bug后顿时让我产生了兴趣,我不知道如果微软的MVC项目开发人员看到这个bug,他是如何解释的。接下来让我就来一个非官方解释吧,欢迎拍砖!
我们可以在第18行设置好断点,然后用VS2010 Debug程序,发现执行到第 21 行 return Json 时 VS未报任何bug。过数秒后断点直接定位在第18行,提示也很简单:未将对象应用到实例。这个确实是我们预期想要的 Exception 但是它始终没有被 catch 住!通过StackTrace我发现了这么一句提示: at System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) 。上述代码第21行return Json 被执行后返回了 JsonAction 这个 JsonAction 被 InvokeActionResult 调用?难道这个就是问题的关键所在?原因是写有try catch 函数(本例的SaveTest()函数)的外层调用函数(InvokeActionResult()函数)出现了异常。也就是外层函数出现异常我们的内层 函数SaveTest()又怎能try catch 到这个外层异常呢?于是通过Reflector进一步证实我的想法。具体的请看下图:
主要是这句action.ExecuteResult(ControllerContext);它的执行过程如下:
最后一幅图的JavaScriptSerializer serializer = new JavaScriptSerializer(); 进行 json 序列化时这个外层函数报出了异常。这个异常在我们的内层函数 SaveTest() 方法内是不可能被截获到的。此刻我已完全弄明白这个无法被catch 的exception 是怎么出现的!至于这个异常为什么会被外层函数触发,主要是.net Linq 延迟加载机制导致的,这不属于本文讨论的范畴,具体的园子内很多文章已经解释的很清楚了。
为消灭掉这个截获不到的exception现附上解决方案:
MVC虽然很年轻,但是这个架构真的很优秀我们可以非常方便对其扩展(我不是MVC的托,呵呵)。为了解决问题,我们只要自定义一个特性该特性继承 HandleErrorAttribute 特性即可。接着重写基类的 OnException 虚方法。代码如下:
public class CustomHandleErrorAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext Context) { base.OnException(Context); dynamic ex = Context.Exception; if (!Context.ExceptionHandled) return; //TODO:将 ex 错误对象记录到系统日志模块 } }
接着我们在MVC Controller 内这样调用:
/* *截获InvokeActionResult 调用 actionResult参数的 ExecuteResult 方法。 ExecuteResult 方法执行时 进行 以下操作: JavaScriptSerializer serializer = new JavaScriptSerializer(); response.Write(serializer.Serialize(this.Data)); 将C# 匿名对象序列化成json数据供 jquery ajax 方法回调。 */ [CustomHandleError] public JsonResult SaveTest() { //try //{ //TestMehod(); //LinqToSql:返回IQueryable数据集合。 var iQueryableData = (from o in _Context.Orders//.Where(o => o.OrderID == 10248) select new { ShipName = o.ShipName, Employee = o.Employee, }).ToList(); //LINQ:返回IEnumerable集合。 var iEnumerableData = from d in iQueryableData select new { ShipName = d.ShipName, EmployeeName = d.Employee.LastName //空引用未处理引发不可截获的异常。 }; return Json(new { Success = true, Msg = iEnumerableData }, JsonRequestBehavior.AllowGet); //Json序列化。 //} //catch //外层错误,导致内层函数catch失效,无法有效的截取错误信息。 //{ // return Json(new { Success = false, Msg = "操作失败。" }, JsonRequestBehavior.AllowGet); //} }
代码看上去更简洁了吧?连try catch 都不用写了更不用担心啥时候这个截获不到的exception神秘人物登场,这样噩梦结束了。。。
本例代码这里下载