随笔 - 313  文章 - 1  评论 - 12178  阅读 - 250万

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

摘要

在《现存问题以及解决方案:在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. 胃疼…………
本贴子以“现状”提供且没有任何担保,同时也没有授予任何权利
This posting is provided "AS IS" with no warranties, and confers no rights.
posted on   Dflying Chen  阅读(7060)  评论(12编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)

点击右上角即可分享
微信分享提示