您的位置:首页 > 编程语言 > ASP

现存问题以及解决方案:在ASP.NET AJAX中从客户端向服务器端传送DataTable

2007-10-08 22:07 786 查看
摘要

在《现存问题以及解决方案:在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

ASP.NET 2.0 AJAX Futures January CTP
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐