代码改变世界

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

2006-12-26 10:48 Jeffrey Zhao 阅读(...) 评论(...) 编辑 收藏

最近被问及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可以利用缓存,不是吗?