随笔-313  评论-12138  文章-1  trackbacks-256

现存问题以及解决方案:在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. 胃疼…………
posted on 2006-11-17 22:00 Dflying Chen 阅读(6182) 评论(15) 编辑 收藏

评论:
#1楼 2006-11-17 23:15 | 水果阿生      
写的好,支持继续写加油啊。
 回复 引用 查看   
#2楼 2006-11-17 23:19 | 老刘.      
感谢:)
 回复 引用 查看   
#3楼[楼主] 2006-11-17 23:22 | Dflying Chen      
@水果阿生
谢谢大哥支持!这个问题确实不太好搞的:)

 回复 引用 查看   
#4楼[楼主] 2006-11-17 23:22 | Dflying Chen      
@老刘.
有帮助就好:)

 回复 引用 查看   
#5楼 2006-11-18 00:00 | Mimi[未注册用户]
真是强人呀!不熟悉Asp.net Ajax,但是我用过一个开源的Ajax框架,Anthem.net, 感觉挺好用的,不知道和微软的Ajax实现有什么区别,能否分析一下。谢谢。
 回复 引用   
#6楼[楼主] 2006-11-18 00:17 | Dflying Chen      
@Mimi
恩,我会在近期总结一下的。Anthem.net确实相当不错!

 回复 引用 查看   
#7楼 2006-11-18 09:47 | 高海东      
我的项目中就用Anthem.net
 回复 引用 查看   
#8楼[楼主] 2006-11-18 11:24 | Dflying Chen      
@高海东
能否分享一下您的心得呢?

 回复 引用 查看   
#9楼 2006-11-19 00:42 | fds2003      
Anthem.net基本是和.net自带的控件使用方式一样,前段时间就是用它做了一个项目,木野狐曾经在总结过http://www.cnblogs.com/RChen/category/69922.html,不过有不好之处就是文档几乎没有。可能我找不到吧,谁有的可否发份给我吗?我使用它开发的时候都是看例子和看源代码,还有就是上官方论坛!总得来说是:简单、方便。
 回复 引用 查看   
#10楼[楼主] 2006-11-19 10:49 | Dflying Chen      
@fds2003
谢谢分享,我也来试试看:)

 回复 引用 查看   
#11楼 2008-03-24 15:03 | Charly      
楼主,我自己做了个DataSetConverter加到了web.config,前段时间一直运行得好好的,但是现在在客户端将DataSet传回服务端的时候,却报“不支持所指定的方法”的错误,这个DataSetConverter是没有问题的(之前一直好好的),现在好像在web.config里对它的引用失效了一样,从服务器端往客户端传DataSet没问题,但是反过来根本就没调用我自己定义的DataSetConverter里的方法,这是什么原因呢?
 回复 引用 查看   
#12楼 2008-03-24 15:17 | Charly      
刚发现我当我传的参数为DataSet的时候,它调用的竟然是DataTableConverter,而不是我在web.config里配置的DataSetConverter。
 回复 引用 查看   
除非特别声明,本站内所有资源,包括但不限于文章,代码,图片等,均应用于Dflying版权说明
关于ASP.NET AJAX,您可以:
直接阅读ASP.NET AJAX文章分类
Atlas文章打包下载(截至4/28/2006)
加入ASP.NET AJAX学习团队
询问关于ASP.NET AJAX的问题
加入ASP.NET AJAX讨论群
阅读愚作《ASP.NET AJAX程序设计》
点击阅读
点击阅读


关于Windows Vista,您可以:
加入Windows Vista开发团队!
昵称:Dflying Chen
园龄:5年10个月
粉丝:127
关注:0

搜索

 
 

最新随笔

随笔分类(352)

随笔档案(313)

Blog Roll

Dflying的其他Blog

Online Chat

统计信息

积分与排名

  • 积分 - 2442908
  • 排名 - 7

最新评论

阅读排行榜

评论排行榜