摘要
ASP.NET AJAX由CTP升級到Beta之後,一個非常常見(我大概聽到了不止50個人的抱怨)的問題就是:在用戶端調用Web Method取得DataTable時候會發生“A circular reference was detected while serializing an object of type 'System.Reflection.Module'.”異常資訊。
本文將分析這個異常產生的原因並給出相應的解決方案,包括異常重現、異常原因、解決方案、範例程式碼下載等部分。
異常重現
讓我們先通過一個簡單的樣本程式重現這個異常,然後基於這個樣本程式修改並解決這個問題。
首先在頁面中聲明一個ScriptManager控制項。由於用戶端DataTable定義與Value-add包中,還需要引入PreviewScript.js指令碼:
<asp:ScriptManager ID="ScriptManager1" runat="server"> <Scripts> <asp:ScriptReference Assembly="Microsoft.Web.Preview" Name="Microsoft.Web.Resources.ScriptLibrary.PreviewScript.js" /> </Scripts></asp:ScriptManager>
接下來聲明一個HTML 按鈕和一個HTML <div>,分別用來引發對Web Method的調用以及顯示出返回的DataTable:
<input id="btnGetDataTable" type="button" value="Get DataTable" onclick="return btnGetDataTable_onclick()" /><div id="result"></div>
上面代碼中,點擊按鈕將調用一個名為btnGetDataTable_onclick()的用戶端JavaScript函數,該函數如下:
function btnGetDataTable_onclick() { PageMethods.GetDataTable(cb_getDataTable);}
可以看到,PageMethods.GetDataTable()即為伺服器端名為GetDataTable()的Web Method的用戶端代理。伺服器端GetDataTable()方法的定義如下,注意該方法必須為靜態(static),且被[System.Web.Services.WebMethod]和[Microsoft.Web.Script.Services.ScriptMethod]兩個屬性所修飾:
[System.Web.Services.WebMethod][Microsoft.Web.Script.Services.ScriptMethod]public static DataTable GetDataTable(){ DataTable myDataTable = new DataTable(); myDataTable.Columns.Add(new DataColumn("Id", typeof(int))); myDataTable.Columns.Add(new DataColumn("Name", typeof(string))); for (int i = 0; i < 10; ++i) { DataRow newRow = myDataTable.NewRow(); newRow["Id"] = i; newRow["Name"] = string.Format("Name {0}", i); myDataTable.Rows.Add(newRow); } return myDataTable;}
上述代碼非常簡單,即建立了一個包含兩個列(Id和Name)的DataTable,並為該DataTable填充了10行資料。
讓我們返回到用戶端JavaScript部分,注意到在調用PageMethods.GetDataTable()時候我們為其指定了一個回呼函數,名為cb_getDataTable(),該JavaScript函數的定義如下:
function cb_getDataTable(result){ var contentBuilder = new Sys.StringBuilder(); for (var i = 0; i < result.get_length(); ++i) { contentBuilder.append("<strong>Id</strong>: "); contentBuilder.append(result.getRow(i).getProperty("Id")); contentBuilder.append(" <strong>Name</strong>: "); contentBuilder.append(result.getRow(i).getProperty("Name")); contentBuilder.append("<br />"); } $get("result").innerHTML = contentBuilder.toString();}
上述回呼函數中只是簡單地對返回的DataTable(result參數)進行格式化後輸出到id為result的<div>中。注意其中使用了Sys.StringBuilder類,用來提高字串拼接效率,還使用了Beta中添加的$get()方法,用來根據id取得某個DOM元素。
這樣就完成了本程式,我們期望著用戶端將能夠正確解析伺服器端返回的DataTable,並將其顯示在頁面中。運行一下樣本程式並點擊頁面中的按鈕,“A circular reference was detected while serializing an object of type 'System.Reflection.Module'.”異常資訊“如我們所願”地出現了。
異常原因
異常原因也非常簡單:伺服器端DataTable中包含了若干個DataRow,而DataRow也包含著對DataTable的引用,自然將造成循環參考。這也正是我們在異常資訊中所見到的。
解決方案步驟一:修改Web.config
得知了原因之後,解決方案也變得明朗起來:自訂DataTable的JSON序列化組件。但ASP.NET AJAX的Value-add中已經為我們提供好了這個組件,即Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter,沒有必要重新發明輪子了。
開啟Web.config檔案,在<microsoft.web>\<scripting>\<webServices>中添加如下的代碼:
<jsonSerialization maxJsonLength="500000000"> <converters> <add name="DataTableConverter" type="Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter"/> </converters></jsonSerialization>
這樣即可使用ASP.NET AJAX的Value-add中內建的DataTableConverter完成對DataTable的序列化了。
如果一切正常的話,這樣也就夠了。滿懷欣喜地再次運行程式並點擊按鈕,卻得到了如下的錯誤資訊:
這是怎麼回事呢?將滑鼠移到result之上,展開之後卻看到一堆亂東西……這根本不是DataTable嘛!搞什麼呢?
解決方案步驟二:自訂JavaScript輔助函數
之所以會看到這一對亂東西,是因為Value-add中內建的DataTableConverter存在著一個Bug(這個Bug也太致命了……),會在序列化後的JSON字串結尾添加一個多餘的結束符。
瞭解了錯誤產生的原因之後,解決方案同樣非常簡單:編寫一個自訂的JavaScript輔助函數,將錯誤的DataTable解析為正確的即可:
function parseBetaDataTable(tbl){ eval('var tblrows = ' + tbl.dataArray.slice(0, tbl.dataArray.length-1) + ';'); tbl = new Sys.Preview.Data.DataTable(tbl.columns, tblrows); return tbl;}
然後在cb_getDataTable(result)中調用這個輔助函數,注意第一行的修改:
function cb_getDataTable(result){ result = parseBetaDataTable(result); var contentBuilder = new Sys.StringBuilder(); for (var i = 0; i < result.get_length(); ++i) { contentBuilder.append("<strong>Id</strong>: "); contentBuilder.append(result.getRow(i).getProperty("Id")); contentBuilder.append(" <strong>Name</strong>: "); contentBuilder.append(result.getRow(i).getProperty("Name")); contentBuilder.append("<br />"); } $get("result").innerHTML = contentBuilder.toString();}
曆經千辛萬苦之後,終於大功告成!
完成後的樣本程式
再次運行頁面並點擊其中的按鈕,將如我們所願地得到正確的運行結果。是不是很有成就感呢?
範例程式碼下載
本文的樣本程式在此下載:ASPNETAJAXDataTable.zip
參考文獻
- http://forums.asp.net/thread/1442553.aspx
- http://forums.asp.net/thread/1251349.aspx
寫作隨想
- 寫文章要認真,做任何事情也是如此。認真兩個字說起來簡單,做起來卻不容易。
- 寫文章要以使用者的角度,用商業的思維考慮。如何讓讀者有興趣,喜歡,讀完,留下評論……這是個學問。
- 研究、解決問題花費一小時,寫作花費一小時。
- Windows Live Writer的Bug太多,寫作過程發生3次異常關閉,好在均有備份,沒有太大損失。
(PS:本文選自我的Atlas著作第II卷,其中第I卷將在明年一月出版,希望大家支援。)