事实上,需要自定义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;
}
{
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);
}
}
……
}
{
public override IEnumerable < Type > SupportedTypes
{
get
{
yield return typeof (Boy);
}
}
……
}
如果要实现一个比较良好的Serialize方法,就需要处理“有循环引用”和“没有循环引用”两种情况。幸运的是,对于Serialize方法来说,这点比较容易:
public
override
IDictionary
<
string
,
object
>
Serialize(
object
obj, JavaScriptSerializer serializer)
{
IDictionary < string , object > result = new Dictionary < string , object > ();
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;
}
{
IDictionary < string , object > result = new Dictionary < string , object > ();
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
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;
}
}
[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 >
< 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 ! ';
}
}
{
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 >
< 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
<
string
,
object
>
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;
}
{
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! " );
}
}
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);" />
< 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;
}
{
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》。
点击这里下载示例。