调用服务器端方法时直接获得客户端具体类型

间隙填充
正睿科技  发布时间:2008-06-30 08:56:14  浏览数:1821

  我们既然需要直接得到一个客户端的具体类型,也就是说,在得到JSON表示的对象之后,还必须做一些额外的事情。而这样的事情应该是服务器端控制的,也就是说在序列化的结果之后,应该将额外的工作“一并输出”。不过可惜的是,在客户端只能输出普通的数据类型,而不能输出“函数”。那么该怎么办呢?

  不过还好,虽然不能输出函数,但是在脚本代码中,函数也是通过字符串的形式表示的,那么我们就用字符串来表示提供“额外工作”的函数吧。于是在这里,我设计了如下协议:

  1. 在序列化输出时,如果需要有额外的工作,在对象中以字符串输出一个“__getRealObject”函数,它的形式是 “function(o){ ... }”,其中参数o就是“__getRealObject”函数所在的对象,而“__getRealObject”函数的返回值则会将该对象替换掉。
  2. 扩展ASP.NET AJAX客户端访问服务器的基础结构,以利用对象输出的“__getRealObject”。

  可以发现,我们在“__getRealObject”函数中其实可以定义“任意工作”,这个作用是非常大的,比如我们能够在客户端构造一个有相互引用的复杂对象。那么我们就开始实现吧:

  为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;
}
}
}

  可以看到,我们定义的 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>

  扩展客户端访问服务器基础结构:

  “官方”的扩展方式是自定义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中的get_object方法,在返回给客户端之前需要进行进一步的处理(使用_getRealObject函数)。我们会首先递归地处理对象中的每个值,然后再对于这个对象应用“__getRealObject”方法,当然前提是用户给出了这个定义。


  使用效果:

  这个例子是在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>

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

  当然,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;
}