• 一起谈.NET技术,在MVC2.0 中 遭遇无法被 Try Catch 的 “Exception” 狼人:


      前天当我为新项目新增完日志模块后对日志模块进行测试,测试时居然发现开发人员一段非常简单的代码,而且很标准的try ... catch .. 写法。代码整理如下:

    public JsonResult SaveTest()
    {
    try
    {
    //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);
    }
    catch (Exception ex)
    {
    return Json(new { Success = false, Msg = ex.Message }, JsonRequestBehavior.AllowGet);
    }
    }

      为方便大家阅读,我用 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神秘人物登场,这样噩梦结束了。

      本例代码这里下载

  • 相关阅读:
    linux grep --我最喜欢的命令~~
    svmrank 的误差惩罚因子c选择 经验
    转:机器学习中的算法(2)-支持向量机(SVM)基础
    转:关于python文件操作大全
    python 求两个时间差
    多个excel合并(excel2007)
    oracle12c 新建表空间
    数据库表被锁了
    join ,left join ,right join有什么区别
    最简洁的权限(菜单)控制
  • 原文地址:https://www.cnblogs.com/waw/p/2163172.html
Copyright © 2020-2023  润新知