在使用ASP.NET AJAX时,大家对于返回服务器端的复杂类型的情况经常会遇到问题。Dflying兄写了一篇文章来说明在如何在客户端得到Sys.Preview.Data.DataTable对象的文章(详见《
现存问题以及解决方案:在ASP.NET AJAX客户端得到服务器端的DataTable
》),但是这种方法似乎从“美学”角度来说并不优雅,而且是针对了DataTable这一个问题所提出的解决方案。
Dflying兄的文章里提到,如果直接使用方法的得到的结果,会出现问题,如下:
Dflying兄对于这一问题的解释是“DataTableConverter”中出现致命Bug。不过, DataTableConverter中的确存在着“不可救药”的低级Bug并不是客户端没有得到Sys.Preview.Data.DataTable 的原因。事实上如果不对ASP.NET AJAX做扩展的话,是无法直接获得一个客户端具体类型的。在ASP.NET AJAX中,从服务器端到客户端的只是一个JSON字符串表示的对象,它又怎么可能从一开始就是一个 Sys.Preview.Data.DataTable对象呢?因此从Dflying兄的解决方案里可以看出,事实上在客户端使用代码构造了一个 Sys.Preview.Data.DataTable对象。
如果我们需要改进这一点,就必须对于ASP.NET AJAX进行扩展。幸运的是,ASP.NET AJAX的可扩展能力非常好,这要归功于JavaScript语言和prototype机制。
1、提出解决方案:
我们既然需要直接得到一个客户端的具体类型,也就是说,在得到JSON表示的对象之后,还必须做一些额外的事情。而这样的事情应该是服务器端控制的,也 就是说在序列化的结果之后,应该将额外的工作“一并输出”。不过可惜的是,在客户端只能输出普通的数据类型,而不能输出“函数”。那么该怎么办呢?
不过还好,虽然不能输出函数,但是在脚本代码中,函数也是通过字符串的形式表示的,那么我们就用字符串来表示提供“额外工作”的函数吧。于是在这里,我设计了如下协议:
2、为DataTable定义JavaScriptConverter并使用:
可以看到,我们定义的DataTableConverter继承了Feature-add(即Beta1的Value-add)程序集中的 Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter, 并覆盖了Serialize方法。可以看到,我们在获得了原来的结果之后,首先改变“dataArray”的值,这个目的是修改原来 DataTableConverter的bug。然后再增加一个“__getRealObject”,添加一个函数,这些还是相当简单的。
然后我们只需修改web.config的配置,让它使用我们的DataTableConverter。如下:
3、扩展客户端访问服务器基础结构:
“官方”的扩展方式是自定义WebRequestExecutor,但是在这里我使用了一个另一个方法。那么直接来看代码吧:
我们在这里改变了Sys.Net.WebRequestExecutor的prototype中的get_object方法,在返回给客户端 之前需要进行进一步的处理(使用_getRealObject函数)。我们会首先递归地处理对象中的每个值,然后再对于这个对象应用 “__getRealObject”方法,当然前提是用户给出了这个定义。
4、使用效果:
这个例子是在Dflying兄上述文章中的例子基础上修改完成。首先是HTML:
然后是JavaScript:
当然,Page Method很重要:
最后来看一下效果吧:
5、不足之处:
Dflying兄的文章里提到,如果直接使用方法的得到的结果,会出现问题,如下:
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();
}
{
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();
}
Dflying兄对于这一问题的解释是“DataTableConverter”中出现致命Bug。不过, DataTableConverter中的确存在着“不可救药”的低级Bug并不是客户端没有得到Sys.Preview.Data.DataTable 的原因。事实上如果不对ASP.NET AJAX做扩展的话,是无法直接获得一个客户端具体类型的。在ASP.NET AJAX中,从服务器端到客户端的只是一个JSON字符串表示的对象,它又怎么可能从一开始就是一个 Sys.Preview.Data.DataTable对象呢?因此从Dflying兄的解决方案里可以看出,事实上在客户端使用代码构造了一个 Sys.Preview.Data.DataTable对象。
如果我们需要改进这一点,就必须对于ASP.NET AJAX进行扩展。幸运的是,ASP.NET AJAX的可扩展能力非常好,这要归功于JavaScript语言和prototype机制。
1、提出解决方案:
我们既然需要直接得到一个客户端的具体类型,也就是说,在得到JSON表示的对象之后,还必须做一些额外的事情。而这样的事情应该是服务器端控制的,也 就是说在序列化的结果之后,应该将额外的工作“一并输出”。不过可惜的是,在客户端只能输出普通的数据类型,而不能输出“函数”。那么该怎么办呢?
不过还好,虽然不能输出函数,但是在脚本代码中,函数也是通过字符串的形式表示的,那么我们就用字符串来表示提供“额外工作”的函数吧。于是在这里,我设计了如下协议:
- 在序列化输出时,如果需要有额外的工作,在对象中以字符串输出一个“__getRealObject”函数,它的形式是 “function(o){ ... }”,其中参数o就是“__getRealObject”函数所在的对象,而“__getRealObject”函数的返回值则会将该对象替换掉。
- 扩展ASP.NET AJAX客户端访问服务器的基础结构,以利用对象输出的“__getRealObject”。
2、为DataTable定义JavaScriptConverter并使用:
namespace
Jeffz
{
public class DataTableConverter : Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter
{
public override IDictionary < string , object > Serialize( object obj, Microsoft.Web.Script.Serialization.JavaScriptSerializer serializer)
{
IDictionary < string , object > result = base .Serialize(obj, serializer);
string dataArray = result[ " dataArray " ].ToString();
if (dataArray[dataArray.Length - 1 ] == ' ) ' )
{
// 顺便fix掉bug
result[ " dataArray " ] = dataArray.Substring( 0 , dataArray.Length - 1 );
}
result[ " __getRealObject " ] = " function(o) { return new Sys.Preview.Data.DataTable(o.columns, eval(o.dataArray)); } " ;
return result;
}
}
}
{
public class DataTableConverter : Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter
{
public override IDictionary < string , object > Serialize( object obj, Microsoft.Web.Script.Serialization.JavaScriptSerializer serializer)
{
IDictionary < string , object > result = base .Serialize(obj, serializer);
string dataArray = result[ " dataArray " ].ToString();
if (dataArray[dataArray.Length - 1 ] == ' ) ' )
{
// 顺便fix掉bug
result[ " dataArray " ] = dataArray.Substring( 0 , dataArray.Length - 1 );
}
result[ " __getRealObject " ] = " function(o) { return new Sys.Preview.Data.DataTable(o.columns, eval(o.dataArray)); } " ;
return result;
}
}
}
可以看到,我们定义的DataTableConverter继承了Feature-add(即Beta1的Value-add)程序集中的 Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter, 并覆盖了Serialize方法。可以看到,我们在获得了原来的结果之后,首先改变“dataArray”的值,这个目的是修改原来 DataTableConverter的bug。然后再增加一个“__getRealObject”,添加一个函数,这些还是相当简单的。
然后我们只需修改web.config的配置,让它使用我们的DataTableConverter。如下:
<
jsonSerialization
maxJsonLength
="500000000"
>
< converters >
< add name ="DataTableConverter" type ="Jeffz.DataTableConverter" />
</ converters >
</ jsonSerialization >
< converters >
< add name ="DataTableConverter" type ="Jeffz.DataTableConverter" />
</ converters >
</ jsonSerialization >
3、扩展客户端访问服务器基础结构:
“官方”的扩展方式是自定义WebRequestExecutor,但是在这里我使用了一个另一个方法。那么直接来看代码吧:
Sys.Net.WebRequestExecutor.prototype._get_object
=
Sys.Net.WebRequestExecutor.prototype.get_object;
Sys.Net.WebRequestExecutor.prototype._getRealObject = function (obj)
{
if (obj && typeof (obj) == 'object' &&
! Array.isInstanceOfType(obj) && ! Date.isInstanceOfType(obj))
{
for (m in obj)
{
var value = obj[m];
obj[m] = this ._getRealObject(value);
}
var strMethod = obj[ " __getRealObject " ];
if (strMethod)
{
delete obj.__getRealObject;
eval( " var method = " + strMethod);
return method(obj);
}
}
return obj;
}
Sys.Net.WebRequestExecutor.prototype.get_object = function ()
{
var obj = this ._get_object();
return this ._getRealObject(obj);
}
Sys.Application.notifyScriptLoaded();
Sys.Net.WebRequestExecutor.prototype._getRealObject = function (obj)
{
if (obj && typeof (obj) == 'object' &&
! Array.isInstanceOfType(obj) && ! Date.isInstanceOfType(obj))
{
for (m in obj)
{
var value = obj[m];
obj[m] = this ._getRealObject(value);
}
var strMethod = obj[ " __getRealObject " ];
if (strMethod)
{
delete obj.__getRealObject;
eval( " var method = " + strMethod);
return method(obj);
}
}
return obj;
}
Sys.Net.WebRequestExecutor.prototype.get_object = function ()
{
var obj = this ._get_object();
return this ._getRealObject(obj);
}
Sys.Application.notifyScriptLoaded();
我们在这里改变了Sys.Net.WebRequestExecutor的prototype中的get_object方法,在返回给客户端 之前需要进行进一步的处理(使用_getRealObject函数)。我们会首先递归地处理对象中的每个值,然后再对于这个对象应用 “__getRealObject”方法,当然前提是用户给出了这个定义。
4、使用效果:
这个例子是在Dflying兄上述文章中的例子基础上修改完成。首先是HTML:
<
asp:ScriptManager
ID
="ScriptManager1"
runat
="server"
>
< Scripts >
< asp:ScriptReference Assembly ="Microsoft.Web.Preview" Name ="Microsoft.Web.Resources.ScriptLibrary.PreviewScript.js" />
< asp:ScriptReference Path ="js/ExecutorExtention.js" />
</ Scripts >
</ asp:ScriptManager >
< input id ="btnGetDataTable" type ="button" value ="Get DataTable" onclick ="return btnGetDataTable_onclick()" />
< div id ="result" ></ div >
< Scripts >
< asp:ScriptReference Assembly ="Microsoft.Web.Preview" Name ="Microsoft.Web.Resources.ScriptLibrary.PreviewScript.js" />
< asp:ScriptReference Path ="js/ExecutorExtention.js" />
</ Scripts >
</ asp:ScriptManager >
< input id ="btnGetDataTable" type ="button" value ="Get DataTable" onclick ="return btnGetDataTable_onclick()" />
< div id ="result" ></ div >
然后是JavaScript:
function
btnGetDataTable_onclick()
{
PageMethods.GetDataTable(cb_getDataTable);
}
function cb_getDataTable(result)
{
// 请注意,result已经是Sys.Preview.Data.DataTable对象了
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();
}
{
PageMethods.GetDataTable(cb_getDataTable);
}
function cb_getDataTable(result)
{
// 请注意,result已经是Sys.Preview.Data.DataTable对象了
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();
}
当然,Page Method很重要:
[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;
}
[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;
}
最后来看一下效果吧:
5、不足之处:
事实上,这个解决方案也不够完美,如果我们的服务器方法返回的是一个DataTable数组呢?我们并没有遍历数组的每一个元素以查看数组内的元素。另 外,如果使用了数组,则会发现数组内的“每个对象”都会存在一个__getRealObject方法,即使他们都是相同的类型。那么应该怎么做呢?可能这 就需要朋友们根据实际情况来做些改进了吧。:)
本文转自 jeffz 51CTO博客,原文链接:http://blog.51cto.com/jeffz/60655,如需转载请自行联系原作者