Hello World
Spiga

浅谈实例Page Method到静态Page Method的移植

2006-12-26 10:48 by 老赵, 3904 visits

最近被问及Page Method的问题比较多,主要还是如何从Atlas CTP中的非静态Page Method转向Beta或RC中的静态Page Method时所遇到的问题。现在我来谈一下在这方面的一些看法,也希望大家能和我一起探讨一下。

在当时,只要在Code Behind的Page类中添加一个实例方法,并且使用WebMethodAttribute进行标记即能使用,例如:

[WebMethod]
public string GetString(string split)
{
    return this.TextBox1.Text + split + this.TextBox2.Text;
}

 

可以看到上面的方法能够通过this引用访问到页面中的控件,然后取它们的值。如果要在客户端调用的话,只要在页面中编写JavaScript代码即可,如下:

function successCallback(result)
{
    ...
}

PageMethods.GetString('|', successCallback);

 

然而在Beta和RC的ASP.NET AJAX中则移除了实例方法的Page Method,于是之前的用法已经完全不兼容了,而且不是那么轻易地就可以进行移植。其实方法总是有的,而且似乎也不是非常的费事,不过在这之前,我们还是来思考一下实例的Page Method与静态的Page Method相关的问题吧。

静态Page Method与实例Page Method的实现方式有什么区别呢?静态的Page Method的实现原理其实和客户端调用Web Service如出一辙,几乎没有任何的区别。而实例Page Method在调用时和UpdatePanel有些接近——至少在一开始,因为它们为了获得整个控件树的信息,必须将页面上的所有内容,例如所有输入控件的值,连同ASP.NET存放在客户端的ViewState等信息一并发送到服务器端。喔,这不就相当于一个完整的PostBack吗?它与静态Page Method相比,最大的劣势也就在这里:性能。实例Page Method在每次调用时都必须传输更多的数据,在构造一棵完整的控件树,换来的是访问控件树的能力——再明确点说,换来的“只是”访问控件树的能力。其它功能的可以说几乎完全没有,例如对于控件的修改无法输出到客户端。

下面我们也必须考虑一下,静态Page Method与实例Page Method相比,究竟有哪些限制呢?如果这种限制对于功能影响太大,那么只能牺牲一些性能了。我们作最坏打算,也不过是相当于作了一次“完整”的刷新么。估计最大的限制就是无法访问页面的“控件树”了,例如下拉框的选项。不过就是因为这一点,我们就必须重建页面的控件树吗?如果是客户端控件的属性,我们可以直接在客户端得它的属性之后,将它作为静态Page Method的参数。如果是其它的值,我们在生成页面时是如何获得的,那么就在静态Page Method中再获得一次吧。其实在我遇到的很多问题中,发现几乎所有的情况都是可以使用“客户端获得数据,再作为服务器端方法的参数”这种做法。例如之前的例子就可以转换成下面的做法。

[ScriptMethod]
public string GetString(string split, string text1, string text2)
{
    return text1 + split + text2;
}

 

function successCallback(result)
{
    ...
}

var text1 = $get('<%= this.TextBox1.ClientID %>').value;
var text2 = $get('<%= this.TextBox2.ClientID %>').value;
PageMethods.GetString('|', text1, text2, successCallback);

 

其实在大多数情况下,从使用实例Page Method移植到使用静态Page Method也不是想象中的那么复杂。然而也有些极端情况下,我们无法轻易地将实例Page Method的使用移植到静态Page Method之上,例如:

  • Page Method里需要使用到控件树的特殊信息,比如结构。
  • 一些信息必须由控件封装好控件,借助ViewState才能得到,例如TreeView控件内的复杂信息。

这该怎么办?还好我们还有UpdatePanel,借助UpdatePanel我们能够在一定程度上进行这种移植。可惜这个做法就不如上一种移植方式优雅了……不过谁叫Page Method的要求如此“过分”呢?一般来说,如果要使用UpdatePanel来模拟实例Page Method功能的话,需要做到以下几步:

  1. 首先,在页面上放置一个空UpdatePanel,UpdateMode设为Conditional,并且指定一个Button作为它的Trigger,如下:

    <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click"  />
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate></ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="Button1" />
        </Triggers>
    </asp:UpdatePanel>

     

  2. 其次,如果原实例的Page Method有参数的话,在页面中放一些<input type="hidden" />用于传递参数,如下:

    <input type="hidden" runat="server" ID="hiddenSplit" />

     

  3. 接着,在页面中编写回调函数和“调用”实例Page  Method的代码:为<input type="hidden" />赋值,再使用JavaScript触发UpdatePenal,如下:

    function successCallback(result)
    {
        ...
    }
    
    $get('<%= this.hiddenSplit.ClientID %>').value = '|';
    $get('<%= this.Button1.ClientID %>').click();

     

  4. 最后,编写服务器端的代码,在作为Trigger的按钮的OnClick事件中,获得存放在<input type="hidden" />里的参数信息,调用原来的Page Method,并将得到的结果使用ScriptManager.RegisterClientScriptBlock方法注册一段脚本,用来执行回调函数,如下:

    protected void Button1_Click(object sender, EventArgs e)
    {
        string result = this.GetString(this.hiddenSplit.Value);
    
        JavaScriptSerializer serializer = new JavaScriptSerializer();
    
        ScriptManager.RegisterClientScriptBlock(
            this.UpdatePanel1,
            this.GetType(),
            "CallbackFunction",
            "successCallback(" + serializer.Serialize(result) + ")",
            true);
    }

     

可以看到,这样的方法除了“能够工作”之外,实在可谓“不忍卒看”。而且,由于触发了Partial Rendering,其它的UpdatePanel可能也会一起进行了刷新,从这里也可以看出,将UpdatePanel的UpdateMode设为Conditional是多么重要。

最后又想起一件事,现在的Page Method已经必须为静态了,那么它和客户端调用Web Service有什么明显的区别的呢?选择哪一个比较好呢?我的答案是:“两者可以说区别”。的确,它们两个再实现上可以说完全相同,它们重用了几乎所有的类,Page Method在ASP.NET 2.0 AJAX Extensions中也使用WebServiceData、WebServiceMethodData用来分析和执行。说到区别,可能只有生成客户端Proxy这一点了吧。Page Method将Proxy直接写在页面上,而Web Service则使用了“.asmx/js”的方式通过<script />元素来引入了客户端Proxy。这么说来,似乎使用Web Service会比较好些——至少生成的Proxy可以利用缓存,不是吗?

Creative Commons License

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

Add your comment

18 条回复

  1. chason[未注册用户]
    *.*.*.*
    链接

    chason[未注册用户] 2006-12-27 00:32:00

    你写的文章我想是挺好,不过语句有些晦涩,读起来有点费劲。以后写文章的时候能不能思维跳跃不要那么快,谢谢!

  2. 老赵
    admin
    链接

    老赵 2006-12-27 00:44:00

    @chason
    谢谢您的意见,我在写blog时比较随意,因此有些地方没有很注意,我会改进的!

  3. chason[未注册用户]
    *.*.*.*
    链接

    chason[未注册用户] 2006-12-27 01:00:00

    对了,如果我有多个参数需要传递的话,还是采用隐藏控件那样传递吗?我觉得这样有些别扭,不过可能我还没有完全看懂你的文章的意思。我以前用ajaxpro,就体验过这样传参数的痛苦,因为不能从codebehind直接读取

  4. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-12-27 01:14:00

    我想知道以前的非静态PageMethod是怎么做到的?我的意思是,在atlas的代码中,如何让Page的ProcessRequest进行到某一个特定的阶段,然后执行PageMethod?

    我没研究过那个,但或许在RC中重现那种做法会更简单。

  5. 老赵
    admin
    链接

    老赵 2006-12-27 01:22:00

    @chason
    如果您使用UpdatePanel来模拟的实例的Page Method的话,我目前想到的办法只有这个了……
    不过如果能够在Client端获取参数的值是最好的了,非常方便,性能又高。

  6. 老赵
    admin
    链接

    老赵 2006-12-27 01:29:00

    @Cat Chen
    我没有看过那时的代码,不过我知道可以怎么做,只是这种做法在目前来说会比较麻烦,所以我选择了现在两种属于很简单的做法。
    要实现非静态的Page Method其实就是调用Page的SetRenderDelegate方法指定一个自定义的Render方法,这样在输出时就不会用默认的方式输出了,会调用你指定的方法。
    UpdatePanel就是用这种方法改变默认输出的,可以看出ASP.NET是多么灵活……
    // 那种复杂的方法,我刚实现到一半,等有空的时候就把它写完,当作一种“实践”,即使不怎么实用,呵呵。

  7. sisi[匿名][未注册用户]
    *.*.*.*
    链接

    sisi[匿名][未注册用户] 2006-12-30 14:09:00

    第二个例子用updatepanel的方法有些看不懂啊

  8. 老赵
    admin
    链接

    老赵 2006-12-30 14:28:00

    @sisi[匿名]
    是哪里看不懂呢?:)

  9. 网鱼[匿名][未注册用户]
    *.*.*.*
    链接

    网鱼[匿名][未注册用户] 2007-01-10 14:18:00

    我的代码如下:
    default.aspx.cs

    using System.Web.Script.Services;
    [ScriptMethod]
    public string GetString(string split, string text1, string text2)
    {
    return text1 + split + text2;
    }

    default.aspx

    <asp:ScriptManager runat="server" ID="scriptManager">
    <Scripts>
    <asp:ScriptReference Assembly="Microsoft.Web.Preview" Name="PreviewScript.js" />
    </Scripts>
    <Services>
    <asp:ServiceReference Path="AJAX.asmx" />
    </Services>
    </asp:ScriptManager>
    <script type="text/javascript">

    var text1 = "123";
    var text2 = "456";

    AJAX.GetString('|', text1, text2, AJAXCallback);
    PageMethods.GetString('|', text1, text2, successCallback);
    function AJAXCallback(result)
    {
    alert("aaa = "+result);
    }

    function successCallback(result)
    {
    alert("xxx = "+result);
    }
    </script>

    我的WEBService可以回传,但你说的方法却得不到值 。
    本来我所有的东西都用WEBSERVICE写好了,可是项目考虑到安全性问题又不用WEBSERVEICE了,所以现在又得改。好郁闷啊,还请老赵帮忙解决一下。

  10. 老赵
    admin
    链接

    老赵 2007-01-10 14:28:00

    @网鱼[匿名]
    您可以尝试着为Page添加一个ScriptServiceAttribute,并且为每个方法添加一个WebMethodAttribute和ScriptMethodAttribute。然后重新启动服务器(或者让应用程序重新启动),应该就可以了。:)

  11. 网鱼[匿名][未注册用户]
    *.*.*.*
    链接

    网鱼[匿名][未注册用户] 2007-01-10 15:43:00

    能说的具体点吗,在page添加WebMethodAttribute是不是指在我的defaule.aspx页面添加阿?可是怎么添加呢,我不会啊,还麻烦你明示一下,谢谢了 。还有为每个方法加 WebMethodAttribute和ScriptMethodAttribute是不是这样的:
    [WebMethodAttribute]
    [ScriptMethodAttribute]
    public string GetString(string split, string text1, string text2)
    {
    return text1 + split + text2;
    }

    不好意思,我是菜鸟,还请见谅!

  12. 老赵
    admin
    链接

    老赵 2007-01-10 15:47:00

    @网鱼[匿名]
    在Code Behind里为您的Page添加ScriptServiceAttribute(似乎也是不需要的),添加的方法就是[ScriptService]。

  13. 网鱼[匿名][未注册用户]
    *.*.*.*
    链接

    网鱼[匿名][未注册用户] 2007-01-10 18:06:00

    还是不行啊,您有测试通过的例子吗,发一个给我吧,谢谢了。
    我的AJAX是RC1版本的。
    我的MAIL:zglg2000@vip.sina.com

  14. 游民一族
    *.*.*.*
    链接

    游民一族 2007-04-06 15:01:00

    弱弱地问一句,现在PageMethod是不是不能用了?我试了好久都没成功啊,呵呵-_-!!!

  15. 老赵
    admin
    链接

    老赵 2007-04-06 15:22:00

    @游民一族
    静态PageMethod自然还是有的……

  16. ljm[未注册用户]
    *.*.*.*
    链接

    ljm[未注册用户] 2007-04-24 17:30:00

    第二个例子在哪?

  17. 老赵
    admin
    链接

    老赵 2007-04-24 20:20:00

    @ljm
    什么第二个例子?

  18. 江湖小弟[未注册用户]
    *.*.*.*
    链接

    江湖小弟[未注册用户] 2007-07-26 08:49:00

    说的好。

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我