现存问题以及解决方案:在ASP.NET AJAX中从客户端向服务器端传送DataTable
摘要
在《現存問題以及解決方案:在ASP.NET AJAX客戶端得到服務器端的DataTable》這篇文章中,我給出了一個在ASP.NET AJAX中從服務器端得到客戶端DataTable的方法,以及相應的示例程序。Jeffrey Zhao更加聰明地對此進行了改進,從根本上解決了從服務器到客戶端傳送DataTable的問題。
然而,這也僅僅解決了這個問題的一半而已。從客戶端向服務器端發送DataTable仍然無法實現,這部分的問題要比前一部分更加嚴重。本文就將分析其中的原因,并給出解決方案。
本文包括如下內容:
?
異常重現——第一個異常:客戶端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
?ASP.NET 2.0 AJAX Futures January CTP
轉載于:https://www.cnblogs.com/Luoke365/archive/2007/10/08/917519.html
總結
以上是生活随笔為你收集整理的现存问题以及解决方案:在ASP.NET AJAX中从客户端向服务器端传送DataTable的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [导入]第 3 章 Enterprise
- 下一篇: wget常用参数