在使用 ASP.NET 的时候,我们仍然在许多情况下需要使用客户端脚本。以下是笔者根据自己的经验和一些粗浅的研究,对此作一个简要的总结。
一、在 HTML 里直接写脚本
这个方法是最简单的,直到如今我写网页的时候也几乎还是使用最多的一种方式。也许一些经常使用 RegisterClientScriptBlock 的人会觉得这种方法老土,不过在我看来,它除了可以减少编译时间以外,更主要的是可以减少代码量,可读性也要好一些,更或许还可以避免一些潜在的错误。
但是有些情况下,直接写的方法是很难完成要求的,比如说当脚本需要依赖我们在代码中动态生成的控件时,就必须要采用 Register 的方法了。
二、使用 Literal 控件写脚本
Literal 控件基本上就是写一段文字或代码,所以当然也可以写出客户端脚本来,实质上这个方法与第一个方法基本相同。以我个人来说,此种方法一般是用在写控件上。设想一个情况:我们需要根据某种条件来判断一批具有客户端脚本事件的控件是否显示,当然我们可以使用如 Panel 一类的容器,可是这些容器多半都要套一个 div 之类的东西,如果在某种情况下我们不希望这时出现 div 呢?当然或许是有更好更漂亮的办法,只是我都是简单地用一个 Literal 了事。^_^
三、使用 Register Script 函数
在 ASP.NET 1.x 中,Page 类提供了 RegisterClientScriptBlock、RegisterOnSubmitStatement 以及 RegisterStartupScript 函数,使得可以在代码中进行 ClientScript 绑定。不过到了 ASP.NET 2.0 当中,将这些函数统通移到了一个 ClientScriptManager 类中,并且还添加了类似的 RegisterClientScriptInclude 函数。在使用这个类的时候,我们可以通过 Page.ClientScript 来访问。下面介绍一下这几个函数的区别。
RegisterClientScriptBlock:这个函数将 ClientScript 加到页面顶部,一般是在紧接着 <form> 标签的地方。需要注意的是,这个函数中的脚本在写入页面甚至执行的时候,页面的其它元素可能还没有载入完毕,因此可能不能正确地调用页面中的元素。这个函数比较常见的应用是一些客户端事件处理函数。
RegisterClientScriptInclude:这个函数是 ASP.NET 2.0 新加入的,它一般用于将一个外部的 ClientScript 文件,比如一个 .js 文件链接入页面的时候。除此之外,它和 RegisterClientScriptBlock 基本一样,包括注意事项。
RegisterStartupScript:这个函数将 ClientScript 加入到页面的底部,由此带来的好处自然就是可以正确地处理对页面元素的引用了。注意这里的脚本将会在页面的 onload 事件之前执行。一般来说,这里的代码都是一次性执行的。最常见的比如说是希望页面载入完毕之后弹出一个消息提示框,又或者在一个有框架的页面中,需要在一个页面装载完毕之后更新另一个页面的情况。
RegisterOnSubmitStatement:这个函数则是将 ClientScript 与 onsubmit 事件绑定起来。
一般的,在使用 RegisterClientScriptBlock 定义了事件处理函数后,我们可以采用以下的代码将函数与控件关联起来,这里,假定我们已经定义了一个 confirmDelete 函数:
2btnDelete.Attributes.Add("onclick", script);
四、关于 RegisterClientScriptResource
除了前一节提过的四个函数外,ASP.NET 2.0 还增加了一个 RegisterClientScriptResource 函数。这个函数与前几个的差异在于:它联接的是经过编译成资源的脚本。比如如下一行
这里的 script_resource.js 必须是在服务端被编译进 assembly 中去,其方法是在服务端代码中添加如下一行
如此一来之后,在生成的页面中,我们可以看到类似下面的代码(为了节省版面,我删减了 d 和 t 的长度):
这里的 WebResource.axd 的请求被送到服务端之后,由一个特定的 axd HttpHandler 来处理以取得相关的资源(这里就是 script-resource.js 文件)。在后面我们还可以看到,这个技术还有着更广泛的应用。
五、GetPostBackClientHyperlink 与 GetPostBackEventReference
这两个函数都可以取得一个字符串,它可以用来作为客户端向服务端提交 PostBack 之用。总体上两者的用途是一样的,主要区别在于:前者返回的串以 "javascript:" 打头,而后者则没有。看以下的例子:
2btnDelete2.Attributes.Add("onclick", cs.GetPostBackEventReference(btnDelete, btnDelete2.ID.ToString());
3linkDelete.HRef = cs.GetPostBackClientHyperlink(btnDelete, linkDelete.ID.ToString());
看看这一段代码生成的相关 HTML 源码:
2<a href="javascript:__doPostBack('btnDelete', 'linkDelete')" id="linkDelete">Delete</a>
从这个例子我们可以看到两个函数的使用,对于超链接的 HRef 来说,应当采用 GetPostBackClientHyperlink,否则浏览器可能不能正确地执行。
六、通过客户端触发服务端事件
有一些情况下,我们需要通过客户端的事件来触发服务端事件。比如说,我们想在一个 TextBox 的 onchange 事件中加一个检测,如果遇上了用户输入回车,那么就触发一个 btnGo 的服务端事件。(当然实际上这个例子我们也可以完全用 javascript 办到,这里仅作说明)。能办到吗?以下是一段代码:
2string script = @"javascript:keyClick(""EVAL_COMMAND"")";
3script = script.Replace("EVAL_COMMAND", strCommand);
4txtSearch.Attributes.Add("onkeydown", script);
以下的相关的 javascript 代码:
2{
3 if (event.keyCode == 13) {
4 eval(cmd);
5 }
6}
好,根据前面所讲的 GetPostBackClientHyperlink 的功能,我们很容易地推断出第一段代码所生成的 HTML 源码:
可以看到在 txtSearch 的 onkeydown 处理函数 keyClick 中,通过 eval 函数再次调用了 btnGo 的服务端事件从而实现了由客户端事件触发 PostBack。是不是有些奇妙?
七、ASP.NET 中的 Ajax?-- Client Callback
Ajax 技术由于其无刷新的页面更新而使许多老式 Web 应用显得极为笨重。并且我们知道有不少 Ajax 其实内部就是 XmlHttpRequest 或是一个 xmlRequestFrame 来实现的,而这两个 IE 早就支持。那么微软有提供基于或是类似于 Ajax 的实现么?我知道最近出了一个 Atalas,但其实除此之外,在 ASP.NET 2.0 中就已经有了实现类似功能的办法了。
首先,我们必须要使页面继承自 ICallbackEventHandler 这个接口。方法可以有如下两种,分别对应于 Code-Inside 和 Code-Behind 模式:
该接口是 ASP.NET 2.0 新加入的。接下来我们要实现它的两个成员方法(在我的本机 MSDN 里,有一些地方使用此接口的成员方法与实际成员不符,比如 RaiseCallbackEvent 的返回类型变成了 string 且没有 GetCallbackResult 方法,我估计是早期写好的但后来没有更新,大家看的时候要注意):
2
3public void RaiseCallbackEvent(String eventArgument)
4{
5 nCount = Convert.ToInt32(eventArgument) + 1;
6}
7
8public string GetCallbackResult()
9{
10 return nCount.ToString();
11}
我们先写好接收回调函数的方法,用 javascript 写:
2{
3 labelResult.innerText = rvalue;
4}
好,随后我们需要将 Callback 链入页面,注意,关键部分到了:
2{
3 ClientScriptManager cs = Page.ClientScript;
4 String cbReference = cs.GetCallbackEventReference(this, "arg", "ReceiveServerData", "context");
5 String callbackScript = "function CallServer(arg, context) {" + cbReference + ";}";
6 cs.RegisterClientScriptBlock(this.GetType(), "CallServer", callbackScript, true);
7}
最后,还需要写好调用的地方:
这里的 value 是需要实现递增的数据。注意递增过程是在 RaiseCallbackEvent 函数内完成的,它就相当于 Ajax 中加上了 AjaxMethodAttribute 的函数。运行一下测试,我们可以发现确实也实现了类似 Ajax 的无刷新页面!
看看生成的 HTML 源码,我们可以看到一行 javascript 脚本资源链接,也是 "WebResource.axd?" 后跟一串参数的一个请求,如同之前讲过的 RegisterClientScriptResource 生成的结果一样。此外还可以看到页面最后有一个用 RegisterStartupScript 生成的一段:
2WebForm_InitCallback();// -->
3</script>
而 CallServer 函数则被扩展成了类似下面的样子:
2{
3 WebForm_DoCallback('__Page',arg,ReceiveServerData,"",null,false);
4}
这里的 WebForm_InitCallback 和 WebForm_DoCallback 显然是在 WebRequest.Axd 的请求所生成的 javascript 文件里,如果我们从 Temporary Internet Files 里打开它,可以看到这两个函数的实现,细细研究一番,能够发现它还是使用了 XmlHttpRequest 和 IFrame 来实现的。有兴趣的朋友们,去研究吧。