随笔-312  评论-12034  文章-2  trackbacks-256

摘要

ASP.NET AJAX由CTP升级到Beta之后,一个非常常见(我大概听到了不止50个人的抱怨)的问题就是:在客户端调用Web Method取得DataTable时候会发生“A circular reference was detected while serializing an object of type 'System.Reflection.Module'.”异常信息。

本文将分析这个异常产生的原因并给出相应的解决方案,包括异常重现、异常原因、解决方案、示例代码下载等部分。

 

异常重现

让我们先通过一个简单的示例程序重现这个异常,然后基于这个示例程序修改并解决这个问题。

首先在页面中声明一个ScriptManager控件。由于客户端DataTable定义与Value-add包中,还需要引入PreviewScript.js脚本:

<asp:ScriptManager ID="ScriptManager1" runat="server">
    <Scripts> 
        <asp:ScriptReference Assembly="Microsoft.Web.Preview" Name="Microsoft.Web.Resources.ScriptLibrary.PreviewScript.js" />
    </Scripts>
</asp:ScriptManager> 

接下来声明一个HTML 按钮和一个HTML <div>,分别用来引发对Web Method的调用以及显示出返回的DataTable:

<input id="btnGetDataTable" type="button" value="Get DataTable" onclick="return btnGetDataTable_onclick()" />

<div id="result">
</div>

上面代码中,点击按钮将调用一个名为btnGetDataTable_onclick()的客户端JavaScript函数,该函数如下:

function btnGetDataTable_onclick() 
{
    PageMethods.GetDataTable(cb_getDataTable);
}

可以看到,PageMethods.GetDataTable()即为服务器端名为GetDataTable()的Web Method的客户端代理。服务器端GetDataTable()方法的定义如下,注意该方法必须为静态(static),且被[System.Web.Services.WebMethod]和[Microsoft.Web.Script.Services.ScriptMethod]两个属性所修饰:

[System.Web.Services.WebMethod]
[Microsoft.Web.Script.Services.ScriptMethod]
public static DataTable GetDataTable()
{
    DataTable myDataTable = new DataTable();
    myDataTable.Columns.Add(new DataColumn("Id", typeof(int)));
    myDataTable.Columns.Add(new DataColumn("Name", typeof(string)));

    for (int i = 0; i < 10; ++i)
    {
        DataRow newRow = myDataTable.NewRow();
        newRow["Id"] = i;
        newRow["Name"] = string.Format("Name {0}", i);

        myDataTable.Rows.Add(newRow);
    }

    return myDataTable;
}

上述代码非常简单,即建立了一个包含两个列(Id和Name)的DataTable,并为该DataTable填充了10行数据。

让我们返回到客户端JavaScript部分,注意到在调用PageMethods.GetDataTable()时候我们为其指定了一个回调函数,名为cb_getDataTable(),该JavaScript函数的定义如下:

function cb_getDataTable(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();
}

上述回调函数中只是简单地对返回的DataTable(result参数)进行格式化后输出到id为result的<div>中。注意其中使用了Sys.StringBuilder类,用来提高字符串拼接效率,还使用了Beta中添加的$get()方法,用来根据id取得某个DOM元素。

这样就完成了本程序,我们期望着客户端将能够正确解析服务器端返回的DataTable,并将其显示在页面中。运行一下示例程序并点击页面中的按钮,“A circular reference was detected while serializing an object of type 'System.Reflection.Module'.”异常信息“如我们所愿”地出现了。

 

异常原因

异常原因也非常简单:服务器端DataTable中包含了若干个DataRow,而DataRow也包含着对DataTable的引用,自然将造成循环引用。这也正是我们在异常信息中所见到的。

 

解决方案步骤一:修改Web.config

得知了原因之后,解决方案也变得明朗起来:自定义DataTable的JSON序列化组件。但ASP.NET AJAX的Value-add中已经为我们提供好了这个组件,即Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter,没有必要重新发明轮子了。

打开Web.config文件,在<microsoft.web>\<scripting>\<webServices>中添加如下的代码:

<jsonSerialization maxJsonLength="500000000">
  <converters>
    <add name="DataTableConverter" type="Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter"/>
  </converters>
</jsonSerialization>

这样即可使用ASP.NET AJAX的Value-add中自带的DataTableConverter完成对DataTable的序列化了。

如果一切正常的话,这样也就够了。满怀欣喜地再次运行程序并点击按钮,却得到了如下的错误信息:

这是怎么回事呢?将鼠标移到result之上,展开之后却看到一堆乱东西……这根本不是DataTable嘛!搞什么呢?

 

 

解决方案步骤二:自定义JavaScript辅助函数

之所以会看到这一对乱东西,是因为Value-add中自带的DataTableConverter存在着一个Bug(这个Bug也太致命了……),会在序列化后的JSON字符串结尾添加一个多余的结束符。

了解了错误产生的原因之后,解决方法同样非常简单:编写一个自定义的JavaScript辅助函数,将错误的DataTable解析为正确的即可:

function parseBetaDataTable(tbl)
{
    eval('var tblrows = ' + tbl.dataArray.slice(0, tbl.dataArray.length-1) + ';');
    tbl = new Sys.Preview.Data.DataTable(tbl.columns, tblrows);
    return tbl;
}

 然后在cb_getDataTable(result)中调用这个辅助函数,注意第一行的修改:

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();
}

历经千辛万苦之后,终于大功告成!

 

完成后的示例程序

再次运行页面并点击其中的按钮,将如我们所愿地得到正确的运行结果。是不是很有成就感呢?

 

示例代码下载

本文的示例程序在此下载:ASPNETAJAXDataTable.zip

 

参考文献

  1. http://forums.asp.net/thread/1442553.aspx
  2. http://forums.asp.net/thread/1251349.aspx

 

写作随想

  1. 写文章要认真,做任何事情也是如此。认真两个字说起来简单,做起来却不容易。
  2. 写文章要以用户的角度,用商业的思维考虑。如何让读者有兴趣,喜欢,读完,留下评论……这是个学问。
  3. 研究、解决问题花费一小时,写作花费一小时。
  4. Windows Live Writer的Bug太多,写作过程发生3次异常关闭,好在均有备份,没有太大损失。

(PS:本文选自我的Atlas著作第II卷,其中第I卷将在明年一月出版,希望大家支持。)

posted on 2006-11-10 22:32 Dflying Chen 阅读(5315) 评论(52)  编辑 收藏 网摘 所属分类: ASP.NET AJAX (Atlas)

评论:
#1楼 2006-11-10 23:10 | MK2      
呵呵,总算可以返回DataTable了,难道在这次Beta2中还没更新这个Bug??
你的新书应该也会出连载预览吧?

  回复  引用  查看    
#2楼[楼主] 2006-11-10 23:13 | Dflying Chen      
@MK2
Beta 2中还没有修复,不过用这个方法已经没问题了。

  回复  引用  查看    
#3楼 2006-11-10 23:51 | Jeffrey Zhao      
我也来抱怨一下:其实我也已经不止一次的谈到要使用JavaScriptConveter,为什么一定要让你写出这篇文章呢?作者要“面向读者”,但是“读者”也应该主动一点吧。为什么情愿等好几天甚至更长时间,却不能花一天时间,研究一下应该怎么解决这个问题呢?为什么很多读者,会喜欢作者“授人以鱼”却不能接受“授人以渔”呢?
  回复  引用  查看    
#4楼[楼主] 2006-11-11 00:00 | Dflying Chen      
@Jeffrey Zhao
写文章要以用户的角度,用商业的思维考虑。
这个很重要,任何领域内的从业人员都是金字塔形结构的。绝大多数的开发者也是还处于金字塔的底层。能够“渔”的前提是“鱼”,而很多开发者却还没有“鱼”的能力,何谈“渔”呢?
如果想让Blog受欢迎,那么“代表最广大人民的根本利益”,是最重要的。

  回复  引用  查看    
#5楼[楼主] 2006-11-11 00:01 | Dflying Chen      
@Jeffrey Zhao
改变能改变的,接受不能改变的。阳春白雪、曲高和寡,可能最后只能落得穷酸文人一般的样子。

  回复  引用  查看    
#6楼 2006-11-11 00:15 | Jeffrey Zhao      
@Dflying Chen
是的,这些我知道。但是有时候不想改变,再坚持一会儿,孔圣人亦是“知其不可为而为之”,我还不想妥协,我还年轻,赫赫,我快乐。我目前算半个理想主义者。
我现在就想寻求一个平衡点,能够在走自己的路时也能照顾到广大人民的根本利益。我也不喜欢曲高和寡阳春白雪,这样孤独。譬如我曾经极力在学校或者向周围的人推广古典音乐,因为我快乐,我相信它也能带给别人快乐。我写Blog也是因为这样我快乐,我喜欢和别人交流的感觉,我喜欢学习也是因为我有满足感,快乐。:)

  回复  引用  查看    
#7楼[楼主] 2006-11-11 00:19 | Dflying Chen      
@Jeffrey Zhao
呵呵,贵在坚持,希望你能把Blog坚持下去,不要像我这样半途而废了。

  回复  引用  查看    
#8楼 2006-11-11 00:36 | Jeffrey Zhao      
@Dflying Chen
你只是换了个方式,没有半途而废,人总是要有选择的嘛。:)

  回复  引用  查看    
#9楼[楼主] 2006-11-11 00:42 | Dflying Chen      
@Jeffrey Zhao
摸索中前行,呵呵……
PS:本贴为聊天专用贴:)

  回复  引用  查看    
#10楼 2006-11-11 02:43 | Cat Chen      
@Jeffrey Zhao
Give a man a fish; you have fed him for today. Teach a man to fish; and you have fed him for a lifetime. 比较糟糕的情况是,当那个人饿到连today都不知道能否挨过时,fishing对他来说是完全没意义的。

我以前也反对求代码的行为,但终于就遇上了一次无法解释也无法解决的问题,花了几天时间也得不到答案。层次相当的人会说,你研究的那个分支我不熟悉,没办法帮忙;层次比你高的人会说,那么简单的东西你自己研究去,我在研究更复杂的东西。这时候我就学会了理解和尊重那些单纯求代码的人。

对于你来说这是花一天研究就能解决的问题,但对其他人来说则不一定是。可能你的一篇文章就足以让他们花上一天才搞懂,那么他们就遇到我上面所说的情况了,同一层次的人帮不了你,高层次的人却觉得研究这个性价比不大。

  回复  引用  查看    
#11楼[楼主] 2006-11-11 08:54 | Dflying Chen      
@Cat Chen
这个分析得挺有道理的

  回复  引用  查看    
#12楼 2006-11-11 09:36 | 老蒋      
不错,学习asp.net ajax中....
  回复  引用  查看    
#13楼[楼主] 2006-11-11 09:52 | Dflying Chen      
@老蒋
呵呵,这个几乎是碰到最多的问题了

  回复  引用  查看    
#14楼 2006-11-11 18:44 | Jeffrey Zhao      
哎,不聊了这些,针对文章内容谈一个问题。
以目前ASP.NET AJAX提供的能力,如果不用你现在的方法,是无法得到一个客户端Sys.Preview.Data.DataTable对象的。即使DataTableConverter没有目前这个问题,得到的也不可能是客户端的DataTable对象。换个角度说“没有得到客户端DataTable”不是因为DataTableConverter的Bug的问题。这点写进书里的时候需要注意一下。:)
// 我想基于你的这篇文章和例子写一篇文章,应该可以吧?:)

  回复  引用  查看    
#15楼 2006-11-11 21:39 | Cat Chen      
@Jeffrey Zhao
写书的话,这些有问题的特性我觉得应该放到后面写吧,先写一些已经稳定的特性。一个特性只要在社区有相当的不满反馈回来,Atlas Team就很可能会去修改它。

  回复  引用  查看    
#16楼 2006-11-11 22:12 | Jeffrey Zhao      
@Cat Chen
JavaScriptConveter这个特性我想应该已经很稳定了吧。
估计以后要改的话,服务器端往往就会围绕着UpdatePanel相关了,Web Service Access这块已经不太会变了……

  回复  引用  查看    
#17楼[楼主] 2006-11-11 22:21 | Dflying Chen      
@Jeffrey Zhao
恩,确实如此,感谢提醒!
这部分内容应该不会放在书中,因为这个Bug显然将很快被修复……
欢迎引用我的文章内容!

  回复  引用  查看    
#18楼[楼主] 2006-11-11 22:21 | Dflying Chen      
@Cat Chen
是的,这部分内容很有可能被删除。

  回复  引用  查看    
#19楼[楼主] 2006-11-11 22:22 | Dflying Chen      
@Jeffrey Zhao
我怎么觉得客户端这一块要改很多呢?

  回复  引用  查看    
#20楼 2006-11-11 22:37 | Jeffrey Zhao      
@Dflying Chen
我觉得已经差不多了阿,文档也已经比较完整了,从CTP -> beta来说这一块变了很多,几乎完全不同了,Beta到正式版这块我觉得就不太会再变了。接下去的变化我觉得更可能只是根据反馈来修改bug。而且Web Access客户端这块是基础结构阿,连Partial Rendering都在使用,要改的话到处都要一起变了……

  回复  引用  查看    
#21楼[楼主] 2006-11-11 22:43 | Dflying Chen      
@Jeffrey Zhao
客户端Preview部分可能还有很大变化啊。

  回复  引用  查看    
#22楼 2006-11-11 23:16 | Jeffrey Zhao      
@Dflying Chen
Preview部分的地位我觉得很尴尬,包括XML-Script,我不知道该如何面对这些东西,ASP.NET AJAX又是如何面对它们的呢?是真的提供了组件还是只是为了兼容CTP的能力?虽然现在“Value-add”已经改名为“Features”包,不过这个Namespace还是让我觉得……呵呵。

  回复  引用  查看    
#23楼[楼主] 2006-11-12 18:04 | Dflying Chen      
@Jeffrey Zhao
XML-Script应该还会添加进去吧,大势所趋……感觉而已

  回复  引用  查看    
#24楼 2006-11-14 20:35 | 晴夜      
@Dflying Chen
终于解决问题 了!真是太感谢了!辛苦了!

  回复  引用  查看    
#25楼[楼主] 2006-11-14 23:06 | Dflying Chen      
@晴夜
有帮助就好!:)

  回复  引用  查看    
#26楼 2006-12-31 15:36 | Anthan      
RC版本下Microsoft.Web.Preview这个程序集已经被集成到System.Web.Extensions里面了是吗?我调试这个例子的时候按钮点击没反应,没有进去btnGetDataTable_onclick()函数里头

  回复  引用  查看    
#27楼[楼主] 2007-01-04 01:15 | Dflying Chen      
@Anthan
没有,还需要手工添加引用才行

  回复  引用  查看    
#28楼 2007-01-05 17:47 | andersling[未注册用户]
Dflying Chen你好!

多谢你的大作,你在文章里头提到是由于致命的bug引起的,请问是在那个类dll当中?我想反编译它根据你的指引直接修复它,必须我现在的项目有太多的地方需要进行类似的改动。多谢!!!请赐教

  回复  引用    
#29楼[楼主] 2007-01-29 11:19 | Dflying Chen      
@andersling
现在的正式版本已经没有这个问题了,呵呵

  回复  引用  查看    
#30楼 2007-02-09 14:39 | ivw[未注册用户]
有个问题想请教你。我看过这人文章后。把例子下载试了下,发现出问题了。不能调用后台代码,控件也加载出错。不知道楼主有没有时间帮我看看呢? 我的ajax在这里下载的。http://ajax.asp.net/
  回复  引用    
#31楼 2007-02-09 14:50 | ivw[未注册用户]
还有提示脚本错误。PageMethods未定义
  回复  引用    
#32楼[楼主] 2007-02-09 20:31 | Dflying Chen      
@ivw
现在这个东西已经不适用了,最新版本中没有这个问题了

  回复  引用  查看    
#33楼 2007-03-10 10:18 | df[未注册用户]
你好,我使用最新的ASP.NET AJAX版本,调用WEBSERVICE方法返回DATATABLE对象,还是会有此错误:A circular reference was detected while serializing an object of type 'System.Reflection.Module'
  回复  引用    
#34楼[楼主] 2007-03-10 11:04 | Dflying Chen      
@df
在最新的ASP.NET AJAX版本中已经没问题了阿,检查一下你的版本是不是最新的?如果不行,贴段代码上来吧:)

  回复  引用  查看    
#35楼 2007-03-10 13:16 | df[未注册用户]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService()]
public class CustWebService : System.Web.Services.WebService {
[WebMethod]
public DataTable WebMethod1(string username)
{
//DataSet ds = new DataSet();
DataTable dt = new DataTable();
DataColumn dc = new DataColumn("aa");
dt.Columns.Add(dc);
DataRow dr = dt.NewRow();
dr["aa"] = "Hello " + username;
//ds.Tables.Add(dt);
return dt;
}}
<script language=javascript>

function GetMyHello()
{
CustWebService.WebMethod1("yy",OnGetComplete);

}
function OnGetComplete(result)
{
var dataTable = result.value;
document.getElementById('result').innerText= dataTable.Rows[0][0].toString();

}
function Button1_onclick() {
GetMyHello();
}
</script>

  回复  引用    
#36楼[楼主] 2007-03-10 17:20 | Dflying Chen      
@df
……没看出什么问题啊

  回复  引用  查看    
#37楼 2007-03-27 12:05 | ole[未注册用户]
eval('var tblrows = ' + tbl.dataArray.slice(0, tbl.dataArray.length-1) + ';');

JS出错提示,'dataArray'为空或不是对象,请指教!!!

  回复  引用    
#38楼[楼主] 2007-03-27 13:52 | Dflying Chen      
@ole
现在不需要parseBetaDataTable了:)

  回复  引用  查看    
#39楼 2007-04-21 01:01 | ylhyh[未注册用户]
@Dflying Chen

你好,我使用的是Ajax的2007.3.2发布的最新版本,想解决本文中谈及的问题,但现版本的Ajax中没有Microsoft.Web.Preview命名空间,该功能是改名了?还是被其它什么代替了?
急盼复

  回复  引用    
#40楼[楼主] 2007-04-21 08:37 | Dflying Chen      
@ylhyh
需要安装CTP部分,在ASP.NET AJAX官方网站上面有下载。

  回复  引用  查看    
#41楼 2007-04-21 23:57 | ylhyh[未注册用户]
@Dflying Chen

非常感谢,以前总不知道CTP是作什么用的,现在了解了一点了
:)

谢谢

  回复  引用    
#42楼[楼主] 2007-04-23 15:36 | Dflying Chen      
@ylhyh
不用客气

  回复  引用  查看    
#43楼 2007-09-11 10:13 | saiyi[未注册用户]
你好,我想问一下,asp.net ajax框架下,客户端调用服务器端函数,必须在同一页面下吗,在其它的页面中可以调用吗?
  回复  引用    
#44楼 2007-11-15 15:08 | lichanghua[未注册用户]
按你的例子在vs2008中做还是出现如下错误:
程序集“Microsoft.Web.Preview, Version=1.2.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”不包含名称为 “Microsoft.Web.Resources.ScriptLibrary.PreviewScript.js”的 Web 资源。

  回复  引用    
#45楼 2007-11-24 15:24 | Valsun[未注册用户]
<add name="DataTableConverter" type="Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter"/>
光有上面的转化还是会报递归循环的错误,我加上
<add name="DataSetConverter" type="Microsoft.Web.Preview.Script.Serialization.Converters.DataSetConverter, Microsoft.Web.Preview"/>
<add name="DataRowConverter" type="Microsoft.Web.Preview.Script.Serialization.Converters.DataRowConverter, Microsoft.Web.Preview"/>
才ok!

  回复  引用    
#46楼 2008-08-27 16:38 | 水边      
我报的错误是:未处理的“System.StackOverflowException”类型的异常出现在 mscorlib.dll 中。

加上DataSet和DataRow的转换后,OK

--引用--------------------------------------------------
Valsun: &lt;add name=&quot;DataTableConverter&quot; type=&quot;Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter&quot;/&gt;
<br>光有上面的转化还是会报递归循环的错误,我加上
<br> &lt;add name=&quot;DataSetConverter&quot; type=&quot;Microsoft.Web.Preview.Script.Serialization.Converters.DataSetConverter, Microsoft.Web.Preview&quot;/&gt;
<br> &lt;add name=&quot;DataRowConverter&quot; type=&quot;Microsoft.Web.Preview.Script.Serialization.Converters.DataRowConverter, Microsoft.Web.Preview&quot;/&gt;
<br>才ok!
--------------------------------------------------------

  回复  引用  查看    



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 557106




相关文章:

相关链接: