在MVC2.0 中 遭遇無法被 Try Catch 的 “Exception”

作者: ryanding  來源: 博客園  發布時間: 2010-11-18 21:58  閱讀: 1464 次  推薦: 0   原文鏈接   [收藏]  

  前天當我為新項目新增完日志模塊后對日志模塊進行測試,測試時居然發現開發人員一段非常簡單的代碼,而且很標準的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神秘人物登場,這樣噩夢結束了。

  本例代碼這里下載

0
0
 
標簽:MVC
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()