現存問題以及解決方案:在ASP.NET AJAX中從用戶端向伺服器端傳送DataTable

來源:互聯網
上載者:User

摘要

在《現存問題以及解決方案:在ASP.NET AJAX用戶端得到伺服器端的DataTable》這篇文章中,我給出了一個在ASP.NET AJAX中從伺服器端得到用戶端DataTable的方法,以及相應的樣本程式。Jeffrey Zhao更加聰明地對此進行了改進,從根本上解決了從伺服器到用戶端傳送DataTable的問題。

然而,這也僅僅解決了這個問題的一半而已。從用戶端向伺服器端發送DataTable仍然無法實現,這部分的問題要比前一部分更加嚴重。本文就將分析其中的原因,並給出解決方案。

本文包括如下內容:

  1. 異常重現——第一個異常:用戶端JSON序列化時發生循環參考造成堆疊溢位
  2. 解決第一個異常——破壞循環參考
  3. 異常重現——第二個異常:伺服器端Deserialize()方法拋出異常
  4. 解決第二個異常——簡單實現Deserialize()方法
  5. 完成後的樣本程式
  6. 範例程式碼下載
  7. 參考文獻

 

異常重現——第一個異常:用戶端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

 

參考文獻

  1. http://forums.asp.net/thread/1442553.aspx
  2. http://forums.asp.net/thread/1251349.aspx
  3. 深入Atlas系列:綜合樣本(1) - 調用伺服器端方法時直接獲得用戶端具體類型
  4. 深入Atlas系列:Web Sevices Access in Atlas樣本(7) - 編寫JavaScriptConverter處理含有循環參考的類型

 

寫作隨想

  1. 本文內容很多,解決問題的方法也比較麻煩
  2. ASP.NET AJAX還是需要很長時間的完善阿
  3. Windows Live Writer的Bug簡直多到不能忍受!!
  4. 分析問題的方法,永遠是最重要的
  5. 胃疼…………
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.