摘要
在《現存問題以及解決方案:在ASP.NET AJAX用戶端得到伺服器端的DataTable》這篇文章中,我給出了一個在ASP.NET AJAX中從伺服器端得到用戶端DataTable的方法,以及相應的樣本程式。Jeffrey Zhao更加聰明地對此進行了改進,從根本上解決了從伺服器到用戶端傳送DataTable的問題。
然而,這也僅僅解決了這個問題的一半而已。從用戶端向伺服器端發送DataTable仍然無法實現,這部分的問題要比前一部分更加嚴重。本文就將分析其中的原因,並給出解決方案。
本文包括如下內容:
- 異常重現——第一個異常:用戶端JSON序列化時發生循環參考造成堆疊溢位
- 解決第一個異常——破壞循環參考
- 異常重現——第二個異常:伺服器端Deserialize()方法拋出異常
- 解決第二個異常——簡單實現Deserialize()方法
- 完成後的樣本程式
- 範例程式碼下載
- 參考文獻
異常重現——第一個異常:用戶端JSON序列化時發生循環參考造成堆疊溢位
本文的將接著《現存問題以及解決方案:在ASP.NET AJAX用戶端得到伺服器端的DataTable》這篇文章中的樣本程式繼續開發。如果你還沒有閱讀過,那麼請先至少熟悉其中的樣本程式。在這篇文章中,我們已經能夠在用戶端得到一個DataTable,其中用戶端的回呼函數如下:
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();}
其中result就是這個用戶端DataTable,讓我們在該函數外定義一個全域的DataTable,將這個DataTable先保留起來:
var m_myDataTable = null;
修改一下上述回呼函數,將result保留在m_myDataTable中:
function cb_getDataTable(result){ result = parseBetaDataTable(result); m_myDataTable = result; var contentBuilder = new Sys.StringBuilder(); //......}
然後在頁面中再添加一個按鈕:
<input id="btnSendDataTable" type="button" value="Send DataTable" onclick="return btnSendDataTable_onclick()" />
onclick中指定的事件處理函數定義如下:
function btnSendDataTable_onclick() { PageMethods.SendDataTable(m_myDataTable, cb_sendDataTable);}
可以看到,PageMethods.SendDataTable()即為伺服器端名為SendDataTable()的Web Method的用戶端代理,我們就通過這個代理將前面儲存起來的DataTable(m_myDataTable)發送回了伺服器。伺服器端SendDataTable()方法的定義如下,注意該方法必須為靜態(static),且被 [System.Web.Services.WebMethod]和 [Microsoft.Web.Script.Services.ScriptMethod]兩個屬性所修飾:
[System.Web.Services.WebMethod][Microsoft.Web.Script.Services.ScriptMethod]public static void SendDataTable(DataTable myDataTable){ // do anything you like. save it to database or xml file, etc.}
樣本程式中我們什麼都沒做,具體應用中各位朋友可以隨心所欲地發揮。我們只要保證DataTable能夠發送過去就行了。
返回到用戶端JavaScript部分,注意到在調用PageMethods.SendDataTable()時候我們為其指定了一個回呼函數,名為cb_sendDataTable(),該JavaScript函數的定義如下:
function cb_sendDataTable(result){ debugger;}
沒什麼講的,只要能夠順利執行到回呼函數,也就是其中的debugger被hit,那麼我們就算是成功了!
這樣就完成了本樣本程式,運行並點擊“Get DataTable”,將順利得到如所示的介面。若出現了異常,請先參考《現存問題以及解決方案:在ASP.NET AJAX用戶端得到伺服器端的DataTable》這篇文章進行修正。
然後點擊“Send DataTable”按鈕,將這個DataTable發送回伺服器…………………………………………在經曆過長時間的等待以及瀏覽器無響應之後,拋出了Out of stack space異常:
右上方的Call Stack中可以看到,同一個函數被調用了無數次——顯然發生了循環參考問題。
讓我們在btnSendDataTable_onclick() 中加上一個斷點,看看這個用戶端DataTable到底是怎麼回事。關於調試JavaScript,請參考我的這篇文章。
在中可以看到,Immediate Window中測試m_myDataTable._rows[0]._owner == m_myDataTable,返回為true。說明確實存在著循環參考:用戶端DataTable的每一個Row對象的_owner屬性都引用回了該DataTable自身,這也就造成了用戶端序列化時無止無休的進行,直至堆疊溢位。
解決第一個異常——破壞循環參考
用戶端DataTable的Row對象的_owner屬性在傳回伺服器時似乎沒什麼用。所以解決這個循環參考問題最好的方式就是,在將用戶端DataTable傳回伺服器之前,清空其每一個Row對象的_owner屬性。
編寫一個輔助函數prepareSendingDataTable(),接受一個用戶端DataTable,返回一個破壞掉循環參考的DataTable:
function prepareSendingDataTable(dataTable){ for (var i = 0; i < dataTable.get_length(); ++i) { dataTable._rows[i]._owner = null; } return dataTable;}
然後修改一下btnSendDataTable_onclick() ,首先調用該輔助函數,然後再發送:
function btnSendDataTable_onclick() { var myDataTable = prepareSendingDataTable(m_myDataTable); PageMethods.SendDataTable(myDataTable, cb_sendDataTable);}
這樣以後,第一個異常——用戶端JSON序列化時發生循環參考造成堆疊溢位就被搞定了!
異常重現——第二個異常:伺服器端Deserialize()方法拋出異常
別高興得太早了——再次運行樣本程式,依次點擊“Get DataTable”和“Send DataTable”兩個按鈕。又出現了如下錯誤:
開啟Fiddler,可以看到如下異常的詳細資料:
仍舊是“System.NotSupportedException”異常……
通過某些手段,我們可以知道ASP.NET AJAX中內建的Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter中根本沒有實現Deserialize()方法,該方法中僅僅是拋出了System.NotSupportedException異常而已……
似乎覺得無語,是嗎?不過ASP.NET AJAX也有它自己的考慮,畢竟DataTable是一個非常複雜的對象。其中Row、Column、資料類型、更新、刪除等各種關係資訊非常複雜。實現這個Deserialize()方法確實將是一個非常浩大的工程。
解決第二個異常——簡單實現Deserialize()方法
有了問題不能逃避。我這裡就簡單地實現了一個DataTable的Deserialize()方法,其中忽略了太多太多的複雜東西。僅僅是建立出了最最最最最最最基本的一個DataTable而已,朋友們可以基於這個進行改進:
using System;using System.Data;using System.Configuration;using System.Collections.Generic;using System.Web;using System.Web.Security;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.WebControls.WebParts;using System.Web.UI.HtmlControls;namespace Dflying.Atlas{ /// <summary> /// Simple implementation of DataTable Converter - Deserialize() method. /// </summary> public class DataTableConverter : Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter { public override object Deserialize(IDictionary<string, object> dictionary, Type t, Microsoft.Web.Script.Serialization.JavaScriptSerializer serializer) { // there's no rows in the DataTable, return null object. Array rowDicts = (dictionary["_array"] as Array); if (rowDicts.Length == 0) { return null; } DataTable myDataTable = new DataTable(); // get column info foreach (string colKey in (rowDicts.GetValue(0) as IDictionary<string, object>).Keys) { myDataTable.Columns.Add(colKey); } // create and add rows to the DataTable foreach (object rowObj in rowDicts) { IDictionary<string, object> rowDict = rowObj as IDictionary<string, object>; DataRow newRow = myDataTable.NewRow(); foreach (DataColumn column in myDataTable.Columns) { newRow[column.ColumnName] = rowDict[column.ColumnName]; } myDataTable.Rows.Add(newRow); } // done! return myDataTable; } }}
注釋非常詳細,這裡不贅。若您不能完全理解,請參考Jeffrey Zhao的一系列非常精彩的深入文章。若您只想著使用的話,那麼也無所謂理解了。
將其放置於App_Code目錄下,並修改Web.config,使用我們自己的DataTableConverter:
<jsonSerialization maxJsonLength="500000000"> <converters> <add name="DataTableConverter" type="Dflying.Atlas.DataTableConverter"/> </converters></jsonSerialization>
千辛萬苦之後,終於大功告成!
完成後的樣本程式
在public static void SendDataTable(DataTable myDataTable)中加上個斷點,再次運行樣本程式,依次點擊“Get DataTable”和“Send DataTable”兩個按鈕。如我們所願,伺服器端得到了正確的DataTable:
接下來,用戶端回呼函數中的debugger也順利被hit。終於搞定……
範例程式碼下載
本文的樣本程式在此下載:ASPNETAJAXDataTable_Send.zip
參考文獻
- http://forums.asp.net/thread/1442553.aspx
- http://forums.asp.net/thread/1251349.aspx
- 深入Atlas系列:綜合樣本(1) - 調用伺服器端方法時直接獲得用戶端具體類型
- 深入Atlas系列:Web Sevices Access in Atlas樣本(7) - 編寫JavaScriptConverter處理含有循環參考的類型
寫作隨想
- 本文內容很多,解決問題的方法也比較麻煩
- ASP.NET AJAX還是需要很長時間的完善阿
- Windows Live Writer的Bug簡直多到不能忍受!!
- 分析問題的方法,永遠是最重要的
- 胃疼…………