Hello World
Spiga

深入Atlas系列:Web Sevices Access in Atlas示例(7) - 编写JavaScriptConverter处理含有循环引用的类型

2006-11-13 16:48 by 老赵, 2494 visits
  有时候在Web Service中会需要使用到比较复杂的类型,它们的特征往往都是含用循环引用,这样的对象如果交给ASP.NET AJAX中默认的序列化方式来处理则会抛出异常,大家经常遇到的“DataTable”问题正是由此引起的。关于这一点,ASP.NET AJAX自然提供了解决方法,在这里“官方”的解决方案就是JavaScriptConverter,它可以让开发人员自定义特定类型的序列化能力。

事实上,需要自定义JavaScriptConverter的类型不止“含有循环引用”的类型,事实上,JavaScriptConverter的目标是“ASP.NET AJAX中无法操作,或者结果不是开发人员所期望那样”的类型,这里的“操作”包括“序列化”于“反序列化”两部分。再举个例子:如果一个类型没有无参数的构造函数,那么也需要定义JavaScriptConverter,否则ASP.NET AJAX无法对其进行反序列化操作。

那么我们就通过一个简单的例子来看一下应该如何开发和使用JavaScriptConverter吧。


1、定义存在循环引用的类型

首先我们定义一个Boy类和Girl类以供使用:
public class Boy
{
    
public string Name;
    
    
public Girl GirlFriend;
}

public class Girl
{
    
public string Name;

    
public Boy BoyFriend;
}

很显然,如果我将它们“配成一对”,在序列化输出时就会抛出异常了。就冲着这点,我们就必须定义一个JavaScriptConverter啊,总不能拆散他俩。


2、定义JavaScriptConverter以及序列化能力

我们下面就该开始定义JavaScriptConverter了,我们姑且将其称之为BoyConverter。首先需要告诉ASP.NET,我们这个Converter可以支持哪些类型:
public class BoyConverter : JavaScriptConverter
{
    
public override IEnumerable<Type> SupportedTypes
    {
        
get
        {
            yield 
return typeof(Boy);
        }
    }
    ……
}

如果要实现一个比较良好的Serialize方法,就需要处理“有循环引用”和“没有循环引用”两种情况。幸运的是,对于Serialize方法来说,这点比较容易:
public override IDictionary<stringobject> Serialize(object obj, JavaScriptSerializer serializer)
{
    IDictionary
<stringobject> result = new Dictionary<stringobject>();
    
    Boy boy 
= (Boy)obj;
    result[
"Name"= boy.Name;

    
// 如果有GirlFriend引用
    if (boy.GirlFriend != null)
    {
        
// 摘除循环引用
        boy.GirlFriend.BoyFriend = null;
        result["GirlFriend"] = boy.GirlFriend;

        
// 在客户端再建立关联
        result["__getRealObject"=
            
"function(o) { o.GirlFriend.BoyFriend = o; return o; }";
    }

    
return result;
}

在这里,我们“手动”地将Boy对象转换为了一个IDictionary<string, objct>,这样就避免出现了循环引用。另外,为了在客户端直接得到一个互相引用的“Boy”和“Girl”对象,我在这里使用了我在前一片文章中提到的扩展,具体请见《深入Atlas系列:综合示例(1) - 调用服务器端方法时直接获得客户端具体类型》。


3、自定义序列化功能使用示例

那么我们来看一下使用示例吧,首先我们需要定义一个Web Service方法:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo 
= WsiProfiles.BasicProfile1_1)]
[Microsoft.Web.Script.Services.ScriptService]
public class BoyGirlService  : System.Web.Services.WebService {

    [WebMethod]
    
public Boy GetBoy(string boyName, string girlName)
    {
        Boy boy 
= new Boy();
        boy.Name 
= boyName;

        
if (!String.IsNullOrEmpty(girlName))
        {
            Girl girl 
= new Girl();
            girl.Name 
= girlName;
            
            girl.BoyFriend 
= boy;
            boy.GirlFriend 
= girl;
        }

        
return boy;
    }    
}

然后是页面中的HTML:
<asp:ScriptManager runat="server" ID="ScriptManager">
    
<Scripts>
        
<asp:ScriptReference Path="ExecutorExtention.js" />
    
</Scripts>
    
<Services>
        
<asp:ServiceReference Path="BoyGirlService.asmx" />
    
</Services>
</asp:ScriptManager>

<input type="button" value="Get Boy with Girlfreind" onclick="GetBoy('Tom', 'Mary');" />
<input type="button" value="Get Boy only" onclick="GetBoy('Tom', null)" />
<br />
<div id="result"></div>

当然还有必须的JavaScript代码:
function GetBoy(boyName, girlName)
{
    BoyGirlService.GetBoy(boyName, girlName, onGetBoySuccess);
}

function onGetBoySuccess(boy)
{
    
if (boy.GirlFriend && boy.GirlFriend.BoyFriend == boy)
    {
        $get('result').innerHTML 
= String.format(
            'The boy is {
0} and his girlfriend is {1}.',
            boy.Name, boy.GirlFriend.Name);
    }
    
else if (!boy.GirlFriend)
    {
        $get('result').innerHTML 
= String.format(
            '{
0} is a boy seeking girlfriend.', boy.Name);
    }
    
else
    {
        $get('result').innerHTML 
= '<b>Error:</b> Something wrong in your code!';
    }
}

当然,最重要的就是在web.config里指定JavaScriptConverter了:
<jsonSerialization maxJsonLength="500">
    
<converters>
        
<add name="BoyConverter" type="Jeffz.BoyConverter"/>
    
</converters>
</jsonSerialization>

打开页面,当点击“Get Boy with GirlFriend”按钮时:


当点击“Get Boy only”时:


我们的这说明我们的JavaScriptConverter生效了!


4、定义反序列化能力

当然,我们可能还需要将Boy对象用作Web Service方法的参数,因此,我们还必须定义反序列化能力。可惜的是,客户端的序列化能力不能简单地自定义,因此我们只能将boy对象在序列化之前先将其“拆开”。

那么我们来看一下Deserialize方法吧:
public override object Deserialize(IDictionary<stringobject> dictionary, Type type, JavaScriptSerializer serializer)
{
    Boy boy 
= new Boy();
    boy.Name 
= dictionary["Name"].ToString();

    
if (dictionary.ContainsKey("GirlFriend"))
    {
        boy.GirlFriend 
= serializer.ConvertToType<Girl>(dictionary["GirlFriend"]);
        boy.GirlFriend.BoyFriend 
= boy;
    }

    
return boy;
}

我们在这里可以利用传入的JavaScriptSerializer来转换Girl对象,只要合理利用JavaScriptSerializer,很多时候我们只需要写一点点代码。这个方法还是非常简单的。


5、自定义反序列化功能使用示例

首先,我们还是写一个Web Service方法:
[WebMethod]
public string ShowBoy(Boy boy)
{
    
if (boy.GirlFriend != null && boy.GirlFriend.BoyFriend == boy)
    {
        
return String.Format(
            
"The boy is {0} and his girlfriend is {1}.",
            boy.Name, boy.GirlFriend.Name);
    }
    
else if (boy.GirlFriend == null)
    {
        
return String.Format("{0} is a boy seeking girlfriend.", boy.Name);
    }
    
else
    {
        
throw new Exception("Something wrong in your code!");
    }
}

接着是HTML代码:
<input type="button" value="Show Boy with Girlfriend" onclick="ShowBoy('Tom', 'Mary');" />
<input type="button" value="Show Boy only" onclick="ShowBoy('Tom', null);" />

最后是JavaScript代码:
function ShowBoy(boyName, girlName)
{
    
var boy = {Name : boyName};
    
    
if (girlName)
    {
        boy.GirlFriend 
= { Name : girlName };
    }
    
    BoyGirlService.ShowBoy(boy, onShowBoySuccess);
}

function onShowBoySuccess(result)
{
    $get('result').innerHTML 
= result;
}

当点击两个按钮时,其效果和之前的例子完全相同,在这里就不重复展示了。


6、总结

ASP.NET AJAX提供的自定义序列化与反序列化能力是通过JavaScriptConverter来提供的,一般来说我们能够很轻松地进行这方面的扩展。另外,合理利用ASP.NET AJAX提供的序列化能力,能够很方便地进行开发。关于ASP.NET AJAX序列化能力的分析,请参考我之前的文章《深入Atlas系列:探究序列化与反序列化能力(上) - 客户端支持,JavaScriptTypeResolver与JavaScriptConverter》和《深入Atlas系列:探究序列化与反序列化能力(下) - JavaScriptSerializer》。



点击这里下载示例。

 
Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

27 条回复

  1. 老赵
    admin
    链接

    老赵 2006-11-13 17:16:00

    这样的文章是不是更容易被接受呢?

  2. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-11-13 18:42:00

    @Jeffrey Zhao
    首先看到“循环引用”字眼,那么站在我的角度我知道这是一个特例所以需要一些特殊的处理方式,所以我会进来看看。但如果有些人连循环引用问题都没了结果,就不会想来看了,就如只会拖放控件的人对动态控件不会有概念。

    但老实说,我也懒得看代码,我知道有个solution在这里就行了,然后可能bookmark到del.icio.us之类的,接着就不看了,因为我不是急需这项信息,我要解决循环问题时会回来看。

    如果是搜索这个问题进来的人,他会仔细看完,但不一定看你的其他文章。不过这里有一个问题——你怎么知道别人用“循环引用”这个字眼做搜索呢?这时候你就要做到Search Engine Friendly了,你要了解一般遇到这类问题的人会碰到什么出错信息,然后他们可能用什么关键字组合来搜索。

  3. 老赵
    admin
    链接

    老赵 2006-11-13 18:52:00

    @Cat Chen
    Search Engine Friendly,有点道理,呵呵。我倒从来没有想过在blog上作SEO,因为可操作的地方太小。不过其实也可以在内容上下功夫的。

  4. Hunts.C
    *.*.*.*
    链接

    Hunts.C 2006-11-13 22:07:00

    @Jeffrey Zhao
    Jeffrey,我的vs 是team suite 版的。现在出现了'Sys' undefied错误,不知道您知不知道怎么解决。根据ReleaseNotes.txt中有这么一句:
    In Visual Studio Team Suite, the Microsoft.Web.Extensions assembly is not listed as an available component……
    而web.config中有“an explicit reference to the assembly in the Web.config file”的呀。 等待您的回复。
    我这次连IIS framework 都一并重装一遍了-_-!

  5. 老赵
    admin
    链接

    老赵 2006-11-13 23:32:00

    @Hunts.C
    您一定是从Beta1转到Beta2的吧?您忘了在Web.config中添加ScriptResource.axd的Handler了。:)

    <handlers>
      <remove name="WebServiceHandlerFactory-ISAPI-2.0"/>
      <add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode" type="Microsoft.Web.Script.Services.ScriptHandlerFactory, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
      <add name="ScriptResource" verb="GET" path="ScriptResource.axd" type="Microsoft.Web.Handlers.ScriptResourceHandler"/>
    </handlers>

  6. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-11-14 00:53:00

    @Jeffrey Zhao
    不知道SEO从什么时候变成了一个纯粹的贬义词,所以我们还是说Search Engine Friendly吧,呵呵……

  7. 老赵
    admin
    链接

    老赵 2006-11-14 01:08:00

    @Cat Chen
    呵呵,那么有技术含量的活儿怎么就这么沦落了呢?

  8. Hunts.C
    *.*.*.*
    链接

    Hunts.C 2006-11-14 08:36:00

    @Jeffrey Zhao
    我不是从beta1转到beta2的,我就差没把vs卸载、重装一遍了,http://forums.asp.net/上有这么个帖子,好像出这个问题的人挺多,且不以前的版本也有这个问题。不过我没找到能解决的办法。http://forums.asp.net/thread/1446560.aspx

  9. 蛙蛙池塘
    *.*.*.*
    链接

    蛙蛙池塘 2006-11-14 09:42:00

    你好,我问个问题,服务端的web服务,会不会让别人给调用了呀,我有点感觉atlas的安全问题,客户端js调用服务端的webservice的时候也没传密码什么的呀?会不会有这个问题,如果已经登录了,调用web服务的时候,web服务的page.user是当前登录的用户吗?

  10. 小蜗牛
    *.*.*.*
    链接

    小蜗牛 2006-11-14 10:36:00

    en,还不错。

  11. 老赵
    admin
    链接

    老赵 2006-11-14 13:12:00

    @Hunts.C
    它提到了几种情况,是不是对您有帮助呢?
    1、您有没有为ScriptResource.axd文件添加Handler呢?
    2、您的ScriptResouce.axd是否在Authenticated Path下面呢?
    3、您是不是使用了IE6 SP1呢?

    如果还是遇到问题的话,您可以手动引入Microsoft ASP.NET AJAX Library的脚本文件。:)

  12. 老赵
    admin
    链接

    老赵 2006-11-14 13:14:00

    @蛙蛙池塘
    您好,您可以在Web.config中将Authentication打开,将asmx放在合适的路径下,只有登陆用户才能访问这个Web Service了,当前的User也是那个登陆用户。:)

  13. 老赵
    admin
    链接

    老赵 2006-11-14 13:21:00

    @蛙蛙池塘
    另外,将传统的信息安全的方法用在您的方法中,虽然不能绝对保护,但是能够在一定程度上保护您的方法不被外界调用了。:)

  14. Hunts.C
    *.*.*.*
    链接

    Hunts.C 2006-11-14 21:10:00

    @Jeffrey Zhao
    1、ScriptResource.axd已经添加Handler了
    2、Authenticated Path? 这个我不懂 您稍微介绍一下怎么操作好吗?非常感谢!
    3、我使用的是IE6 sp2
    4、我使用的是team suite版,Microsoft.Web.Extensions 在GAC中存在

  15. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-11-14 21:35:00

    @Jeffrey Zhao
    不知道呢,可能Google等大公司都在自己的“提交网站”页面说明了禁止SEO,所以SEO就变成了贬义词。

    我觉得SEO,或者说Search Engine Friendly吧,是必须做的。一个网站得到他应得的排名叫做合理SEO,就如合法收入一样;再进行更多的SEO,就算是过度SEO了,就如非法收入一样。

  16. 老赵
    admin
    链接

    老赵 2006-11-14 22:39:00

    @Hunts.C
    TeamSuite不是问题,这是IDE,与运行无关。我说的Authenticated Path就是指您是不是在没有登陆的状况下访问需了要验证的资源呢?还有,您看一下页面中的代码,里面会有<script src="ScriptResource.axd?..."></script>,那么在浏览器里直接访问这个地址得到的结果是什么呢?如果是404的话,查看一下是否它没有注册在IIS中呢?

  17. 老赵
    admin
    链接

    老赵 2006-11-14 22:46:00

    @Cat Chen
    SEO也分恶意不恶意的吧,比如URL Rewrite等等,比如在页面中使用合理的meta,合理的Headline,尽量使用链接而不是JS跳转都是合理的SEO,使用SiteMap甚至都是Google建议的……就算是一些特殊的比如不要出现302和404也是很不错的。至于一些恶意填充信息等就不对了。

  18. Hunts.C
    *.*.*.*
    链接

    Hunts.C 2006-11-15 15:49:00

    @Jeffrey Zhao
    问题OK了,我今天没特意去尝试解决,结果好好的就没问题了,我汗啊,不知所以然的好了的话,我原先的努力就白费了,因为从中没有收获~~而且还打扰了几位园友~~ 在此谢过Jeffrey。
    在没有登陆的状况下访问需了要验证的资源?这句话是什么概念,我还是不懂,您有没有有关的文章介绍一篇,我了解了解:)

  19. 老赵
    admin
    链接

    老赵 2006-11-15 16:11:00

    @Hunts.C
    哎,没帮上忙,不好意思。:)

  20. Shawn[匿名][未注册用户]
    *.*.*.*
    链接

    Shawn[匿名][未注册用户] 2006-12-18 22:02:00

    老赵,看你这篇文章时想到这样一个问题,也是实际项目经常碰到的
    如果两个对象是一个一对多的关系:
    public class Boy
    {
    public string Name;

    public Girl[] GirlFriends;
    }

    public class Girl
    {
    public string Name;

    public Boy BoyFriend;
    }
    直接拿的你的例子,可能不是太合适不过大致表达一下意思。
    这样的序列化和反序列化应该怎么进行呢?
    例子中的result["__getRealObject"] =
    "function(o) { o.GirlFriend.BoyFriend = o; return o; }";
    这段是怎么在客户端起作用的?
    在你别的文章看到说现在的AJAX不能在客户端序列化成一个带有循环引用的对象了,那碰到这种情况应该怎么处理呢,而且这样的情况应该很容易碰到吧

  21. 老赵
    admin
    链接

    老赵 2006-12-18 22:24:00

    __getRealObject是我对于客户端的扩展,您可以看我那篇文章里的说明。我说的“不能在客户端序列化成一个带循环引用的对象”是指不能“直接”做到了,您可以把它拆成没有引用的对象,然后就可以在客户端得到了。:)

  22. 老赵
    admin
    链接

    老赵 2006-12-18 22:25:00

    @Jeffrey Zhao
    漏了说了,在客户端得到之后,再重新建立循环引用,就像__getRealObject的做法那样。:)

  23. Soddy[未注册用户]
    *.*.*.*
    链接

    Soddy[未注册用户] 2007-08-07 02:57:00

    看了一写评论,终于把刚才找了很久的ScriptResource.axd 404错误给解决,直接添加的几个库文件.没办法,虚拟空间的.想问下老赵如果要根本解决这错误,要怎么做?

  24. 老赵
    admin
    链接

    老赵 2007-08-07 11:03:00

    @Soddy
    没办法,只能让服务供应商来添加ScriptResource.axd。

  25. Soddy[未注册用户]
    *.*.*.*
    链接

    Soddy[未注册用户] 2007-08-07 19:19:00

    @Jeffrey Zhao
    具体怎么做呢?.是不是因为版本原因了?我的是XP SP2+VS2005他们的是WIN2003的.

  26. 老赵
    admin
    链接

    老赵 2007-08-07 21:06:00

    @Soddy
    在IIS里注册ISAPI就可以了。

  27. Soddy[未注册用户]
    *.*.*.*
    链接

    Soddy[未注册用户] 2007-08-08 13:58:00

    @Jeffrey Zhao
    多谢老赵.

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我