Hello World
Spiga

深入Atlas系列:综合示例(1) - 调用服务器端方法时直接获得客户端具体类型

2006-11-11 23:04 by 老赵, 4488 visits
  在使用ASP.NET AJAX时,大家对于返回服务器端的复杂类型的情况经常会遇到问题。Dflying兄写了一篇文章来说明在如何在客户端得到Sys.Preview.Data.DataTable对象的文章(详见《现存问题以及解决方案:在ASP.NET AJAX客户端得到服务器端的DataTable》),但是这种方法似乎从“美学”角度来说并不优雅,而且是针对了DataTable这一个问题所提出的解决方案。

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

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表示的对象之后,还必须做一些额外的事情。而这样的事情应该是服务器端控制的,也就是说在序列化的结果之后,应该将额外的工作“一并输出”。不过可惜的是,在客户端只能输出普通的数据类型,而不能输出“函数”。那么该怎么办呢?

不过还好,虽然不能输出函数,但是在脚本代码中,函数也是通过字符串的形式表示的,那么我们就用字符串来表示提供“额外工作”的函数吧。于是在这里,我设计了如下协议:
  1. 在序列化输出时,如果需要有额外的工作,在对象中以字符串输出一个“__getRealObject”函数,它的形式是“function(o){ ... }”,其中参数o就是“__getRealObject”函数所在的对象,而“__getRealObject”函数的返回值则会将该对象替换掉。
  2. 扩展ASP.NET AJAX客户端访问服务器的基础结构,以利用对象输出的“__getRealObject”。
  可以发现,我们在“__getRealObject”函数中其实可以定义“任意工作”,这个作用是非常大的,比如我们能够在客户端构造一个有相互引用的复杂对象。那么我们就开始实现吧:


2、为DataTable定义JavaScriptConverter并使用:
namespace Jeffz
{
    
public class DataTableConverter : Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter
    {
        
public override IDictionary<stringobject> Serialize(object obj, Microsoft.Web.Script.Serialization.JavaScriptSerializer serializer)
        {
            IDictionary
<stringobject> 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>


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中的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>

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

最后来看一下效果吧:



5、不足之处:

事实上,这个解决方案也不够完美,如果我们的服务器方法返回的是一个DataTable数组呢?我们并没有遍历数组的每一个元素以查看数组内的元素。另外,如果使用了数组,则会发现数组内的“每个对象”都会存在一个__getRealObject方法,即使他们都是相同的类型。那么应该怎么做呢?可能这就需要朋友们根据实际情况来做些改进了吧。:)


点击这里下载示例。
Creative Commons License

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

Add your comment

18 条回复

  1. 老赵
    admin
    链接

    老赵 2006-11-11 23:26:00

    哎……

  2. neuhawk
    *.*.*.*
    链接

    neuhawk 2006-11-11 23:44:00

    wpf/e可以用c#编程,希望它能解决与.net类型兼容的问题.
    对于太复杂的js,一般人都不想去维护.

  3. 老赵
    admin
    链接

    老赵 2006-11-11 23:59:00

    @neuhawk
    JavaScript还是不能放弃的,WPF/E是MS的而JavaScript或者说ECMAScript是标准。其实现在JavaScript的使用已经可以越来越规范了,再加上一些工具的支持,维护起来感觉还是能够让人满意的。

  4. neuhawk
    *.*.*.*
    链接

    neuhawk 2006-11-12 00:06:00

    我是十分不喜欢写js的.现在满街都鼓吹web 2.0 ajax,但对于我们做行业应用软件来说,html+js太弱了.所以我一直期待ria,可是flash和flex都不怎么样,很多东西还没有办法实现,现在期待一下wpf/e,估计要出ctp了.

  5. 老赵
    admin
    链接

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

    @neuhawk
    我觉得大都Web 2.0应用是在抄概念和跟风。至于html+js再强,还是在负责处理表现层,不能把它看成银弹。该用则用,不适合则不要用。

  6. neuhawk
    *.*.*.*
    链接

    neuhawk 2006-11-12 00:15:00

    问题是,表现层也不够用啊.比如sap的UI,用html+js没有办法实现的.实现了也难象sap那样容易扩展和配置.当然,crm还是用web好.

  7. 老赵
    admin
    链接

    老赵 2006-11-12 00:22:00

    @neuhawk
    AJAX的作用是提高表现的丰富性,而不是万能药啊,当然不可能适合所有的应用阿。如果都完美了都足够了,技术不就不用发展了吗?:)

  8. neuhawk
    *.*.*.*
    链接

    neuhawk 2006-11-12 00:27:00

    我对atlas比较失望的是,没有象其他js框架那样提供丰富的UI,如果使用updatePanel+服务控件,有些功能我觉得很难实现啊,而且也不算"正宗"的ajax.asp 3.0说不定以ajax为核心,这样的话,变化很大的哦.

  9. 老赵
    admin
    链接

    老赵 2006-11-12 00:36:00

    @neuhawk
    我觉得这并没有什么不好,已经有别的框架提供了很多UI组件,ASP.NET AJAX应该提供的是基础建设,现在感觉已经作的不错的啊,ASP.NET Beta文档也已经提到了要和别的JS框架兼容了,再重复劳动不太好。还有为什么说不是正宗的AJAX呢?
    至于ASP.NET 3.0,大家都听到了什么消息了吗?我没有听到任何风声……

  10. neuhawk
    *.*.*.*
    链接

    neuhawk 2006-11-12 00:42:00

    问题是,别的框架跟.net集成,绑定<-->保存,累死.
    java很多ajax框架,已经实现象asp.net控件那样方便使用了.
    如果把yui等的东西结合.net,还真有点累哦.atlas没出来的时候,可是吹的很厉害了.

  11. 老赵
    admin
    链接

    老赵 2006-11-12 00:53:00

    @neuhawk
    我觉得如果推出控件可能利用率会不太高,比如DataGrid,Menu,TreeView这种,在真的应用中又使用了多少呢?用的最多还是Repeater,Label甚至Literal这种最简单的控件,现在应用的变化已经越来越多了。至于Atlas在宣传时似乎也没有提到会提供多种多样的控件吧,可能是别人的作法都是推出很多控件,于是以为Atlas也会这么做了。可能以客户端为中心的开发人员喜欢的还是基础结构吧。

  12. neuhawk
    *.*.*.*
    链接

    neuhawk 2006-11-12 01:01:00

    虽说不是常用,但不得不用啊,比如iewebcontrol的tab控件,非常需要的.
    我用datagrid>>Repeater,因为Repeater不好动态控制,
    对于输入的界面,特别是grid,要求还是比较多的.对于报表查询界面,Repeater比较好用.

  13. 老赵
    admin
    链接

    老赵 2006-11-12 01:11:00

    @neuhawk
    其实一个控件要做的可用,其丰富的定制性是必要的,但是定制能力要做到什么样才能真正适合各种应用呢?还有DataGrid,可能是您接触这些行业应用比较多的缘故吧。像GridView这种,要利用其丰富的功能,往往就需要打开ViewState,而这对于在大多数应用会比影响性能,尤其是GridView这种数据量大的控件。而且GridView再怎么说还是表格,在目前样式越来越丰富的应用前,还是不够灵活啊。

  14. neuhawk
    *.*.*.*
    链接

    neuhawk 2006-11-12 01:23:00

    java eye:
    关于什么样的框架是一个好的ajax框架:
    1, 包含完整的客户界面元素的封装,
    2, 包含布局方式的封装,
    3, 包含对动画效果,多媒体文件的封装,
    4, 包含对事件机制的封装,
    5, 包含数据操作的封装(xpath),
    6, 包含rpc对象的封装
    optional:
    7, 和普通html融合
    8, 图表表格矢量图封装

  15. 老赵
    admin
    链接

    老赵 2006-11-12 01:31:00

    @neuhawk
    一家之言吧。:)
    另外有些东西我觉得已经超过了ajax框架的范畴,很多功能是评判“JS框架”的依据吧。虽然AJAX框架总是基于JS的,但是应该还是以提供AJAX功能为主。其它的功能可以依靠结合优秀JS框架得到。
    还有就是ASP.NET AJAX做到的是与ASP.NET的无缝集成,另外还有良好的组件封装的能力,所以才有ControlToolkit的出现,这个我很喜欢。
    再者忽然想到,其实丰富的客户端控件应该从一开始就不是ASP.NET AJAX的设计目标吧。:)

  16. 老赵
    admin
    链接

    老赵 2006-12-31 11:34:00

    @无忧
    这是……

  17. kevin[未注册用户]
    *.*.*.*
    链接

    kevin[未注册用户] 2007-08-09 17:59:00

    能否从客户端向服务器端传输动态数组,我遇到的问题是:客户端接受或者发送到服务器端的数据个数是变化的,请问赵老师如何实现?想用动态数组,不只是否可行?请赵老师指点。

  18. 老赵
    admin
    链接

    老赵 2007-08-09 18:12:00

    @kevin
    动态数组?在服务器段或者客户端创建数组然后传就可以了,长度不可变的数组完全够用阿。

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我