代码改变世界

探讨微软ASP.NET AJAX控件开发技术(服务器端)

2007-09-13 09:32  Jacky_Xu  阅读(612)  评论(0编辑  收藏  举报

一、简介

到目前为止,我们已经讨论了开发Ajax控件所涉及的客户端相关技术。现在,让我们来讨论此过程中与服务器端相关的一些技术。

需要说明的是,在【客户端】篇中我们的举例本质上仅是使用ASP.NET AJAX框架提供的面向对象JavaScript技术来增强了一个客户端图像组件,而没有明显涉及到AJAX技术(除了ScriptManager在后台以AJAX方式下载并管理客户端脚本代码外)。所以,这个例子是简单的,仅凭客户端相关知识就可以使用这个增强控件。

但是,在实际开发中,当要增强的客户端控件涉及到AJAX技术时,或者干脆是想增强服务器端组件(如UpdatePanel控件)时,我们必须进行相关的服务器端编程,而这要求我们必须对Ajax控件开发中所涉及的服务器端相关联的类有所了解。而且,还要以ASP.NET 2.0服务器控件开发相关知识为基本前提,特别是在开发复杂的Ajax控件时。

在本篇中,我们要重新构造一个增强的图像按钮控件MySrvImageButton,此控件将以ASP.NET 2.0服务器控件ImageButton为基础。

二、AJAX控件开发服务器端相关技术

首先,让我们来看一下AJAX控件开发服务器端相关组件及其关系,这些类之间的继承关系图如下图1所示。

图1:控件开发涉及的主要服务器端类之间层次结构图

上图展示了组件、控件和扩展器之间的继承关系。如你所见,为了开发一个控件(注意,Component和Extender不在本文讨论范围之内),我们有两个选择:其一,创建一个派生自ScriptControl的类;其二,创建一个实现IScriptControl接口的类。但是,如果你想使你的控件从WebControl派生,那么,ScriptControl应该是一个更好的选择—因为它正是派生自WebControl控件本身。但是,如果你想从头开发创建你的控件,并且不要求实现WebControl所具备的任何内在特征,那么,实现IScriptControl则更为恰当。此外,当你想在一个现有控件(例如本文中的MySrvImageButton)中添加Ajax特征时选择使用接口IScriptControl也会是你的选择。但是这两种方法都要求重载下列两个方法:①、GetScriptDescriptors;②、GetScriptReferences。

三、GetScriptDescriptors

这个方法负责在客户端以自动方式生成$create语句(而在上篇中是使用手工方式)。它使用了一个特殊类ScriptDescriptor来生成它。在继续往下讨论前,首先让我们来浏览一下ScriptDescriptor类中的继承层次图(如图2所示):

图2:ScriptDescriptor类中的继承层次图

显然,每一种具体类型的组件都存在其单独的描述符。如果你在开发一个常规组件,那么,这个方法会返回一个ScriptComponentDescriptor的实例。对于扩展器而言,该方法返回的是一个ScriptBehaviorDescriptor实例;而对于一个控件,则返回ScriptControlDescriptor类的实例。该描述符中提供了一些特定的方法用于创建服务器端与客户端的“连接”。下面,还是让我们来分析一个有关$Create语句是如何在服务器端“注入”的简短示例:

//上篇中从客户端以手工方式使用$create
$create(AjaxImageButtonNamespace.MyCliImageButton,

{'hoverImageUrl':'Images/updateh.gif'},

{'click':buttonClicked}, null, $get('cliBtn'));
在本文中,我们在服务器端以下列几个语句共同自动生成$Create语句:
ScriptControlDescriptor desc =

new ScriptControlDescriptor("AjaxImageButtonLib.MySrvImageButton",
ClientID);
if (!string.IsNullOrEmpty(HoverImageUrl))
{
desc.AddProperty("hoverImageUrl", HoverImageUrl);
}
if (!string.IsNullOrEmpty(ClientClickFunction))
{
desc.AddEvent("click", ClientClickFunction);
}
return desc;

在上面的代码中,我们在构造器中传递客户端类型(尽管客户端和服务器端具有相同的类型名)和DOM元素ID。这一步将填充$Create语句的第一个和最后一个参数。此后,我们设置hoverImageUrl属性,它对应于$Create语句的属性部分。最后,通过设置Click事件处理器,我们填充$Create语句的事件部分。下面是脚本描述符中暴露的几个重要的方法:

1)AddProperty()
这个方法能够在客户端添加一个属性。第一个参数相应于属性名,第二个参数对应该参数的取值。
2)AddEvent()
这个方法在客户端添加一个事件。第一个参数相应于事件名,第二个参数对应你想绑定的函数的名称。
3)AddScriptProperty()
这个方法能够把JavaScript代码指定为属性的一个值。对于复杂的属性赋值通常都要求这样的操作。
4)AddElementProperty()
这个方法在客户端添加一个属性,但是其与AddProperty方法间的区别在于,该值作为一个参数传递给$get方法。
5)AddComponentProperty()
这个方法负责在$Create语句的Component部分添加一个组件引用。该值将被用于$find语句中实现属性的赋值。这个方法的第一个参数相应于属性名,第二个参数对应该组件的id。

四、GetScriptReferences

这个方法负责在ScriptManager中注册JavaScript文件。在这个方法中,针对每一个要求的JavaScript文件,我们都需要创建一个相应的ScriptReference实例。例如,在图像按钮例子中,我们按如下方式注册JavaScript文件:

IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
{
return new ScriptReference(Page.ResolveUrl("~/SrvImageButton.js"));
}

此外,我们还可以以嵌入式资源方式注册JavaScript文件。为此,我们必须首先在Visual Studio的解决方案资源管理器中把一个JavaScript文件标记为一种嵌入式资源。然后,我们必须添加上WebResource属性以便利用Asp.Net 2.0 Web资源处理器。下列代码展示了如何把一个JavaScript文件注册为一个嵌入式资源:

[assembly: WebResource("MyControls.SubControl.Script1.js", "text/javascript")]
namespace MyControls{
public class SubControl: Control, IScriptControl
{
//………
IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
{
return new

ScriptReference(this.Page.ClientScript.GetWebResourceUrl(
this.GetType(), "MyControls.SubControl.Script1.js"));
}
}
}

此外,如果我们想实现IScriptControl接口而不是从ScriptControl中继承的话,我们还必须重载OnPreRender与Render方法。这样将能确保ScriptManger识别出该服务器控件是一个支持Ajax功能的控件。

下列代码展示了开发基于ASP.NET 2.0服务器端控件的AJAX控件时必须在服务器端实现的最少代码:

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace DummyNamespace
{
public class DummyControl : Control, IScriptControl
{
public DummyControl(): base()
{}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
ScriptManager scriptManager = ScriptManager.GetCurrent(Page);
if (scriptManager == null)
{
throw new InvalidOperationException(
"此页面中必须存在一个ScriptManager控件!");
}
scriptManager.RegisterScriptControl(this);
}
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID);
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.Write("这是一个哑元控件");
writer.RenderEndTag();
if (!DesignMode) {
ScriptManager.GetCurrent(this.Page).RegisterScriptDescriptors(this);
}
}
IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()
{
//因为这仅是一个哑元控件,所以我们只是创建一个新的实例
return new ScriptControlDescriptor("DummyNamespace.DummyControl", ClientID);
}
IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
{
return new ScriptReference(Page.ResolveUrl("~/DummyControl.js"));
}
}
}

上面代码中,我们在OnPreRender方法中实现注册控件,而在Render方法中注册脚本描述符部分。当然,如果页面上不存在ScriptManager控件的话,我们必须抛出一个错误提示。但是,如果你正在开发非基于ASP.NETAJAX框架的控件的话,你完全可以从页面中删除ScriptManager控件。

五、创建基于ASP.NET服务器端控件的增强AJAX图像控件

(一)创建示例AJAX网站

启动Visual Studio 2005,选择“文件→新建网站…”,然后选择“ASP.NET AJAX-Enabled Web Site”模板,命名工程为“AjaxServCtrlTest”,并选择C#作为内置支持语言,最后点击“确定”。

(二)创建AJAX技术支持的增强服务器控件

点击菜单“文件→添加→新建项目…”,在“添加新项目”对话框中,从左边选择“项目类型”为“Visual C#→Windows”,从右边选择“模板类型”为“Web控件库”,输入控件库的名字为AjaxImageButtonLib,选择目标目录为前面创建的网站根目录,最后点击“确定”。

接下来,根据我们前面的分析,把类库源WebCustomControl1.cs文件的内容更改为以下形式:

//…………(省略命名空间引用部分)
namespace AjaxImageButtonLib
{
public class MySrvImageButton :

System.Web.UI.WebControls.ImageButton, IScriptControl
{
public string HoverImageUrl
{
get
{
object value = ViewState["hoverImageUrl"];
return (value == null) ? string.Empty : (string)value;
}
set
{
ViewState["hoverImageUrl"] = value;
}
}
public string ClientClickFunction
{
get
{
object value = ViewState["clientClickFunction"];

return (value == null) ? string.Empty : (string)value;
}
set
{
ViewState["clientClickFunction"] = value;
}
}
public MySrvImageButton()
: base()
{
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
ScriptManager scriptManager = ScriptManager.GetCurrent(Page);
if (scriptManager == null)
{
throw new InvalidOperationException

("ScriptManager required on the page.");
}
scriptManager.RegisterScriptControl(this);
}
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
if (!DesignMode)
{
ScriptManager.GetCurrent(this.Page).RegisterScriptDescriptors(this);
}
}
IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()
{
ScriptControlDescriptor desc =

new ScriptControlDescriptor("AjaxImageButtonLib.MySrvImageButton", ClientID);
if (!string.IsNullOrEmpty(HoverImageUrl))
{
desc.AddProperty("hoverImageUrl", HoverImageUrl);
}
if (!string.IsNullOrEmpty(ClientClickFunction))
{
desc.AddEvent("click", ClientClickFunction);
}
yield return desc;
}
IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
{
yield return new ScriptReference(Page.ResolveUrl("~/SrvImageButton.js"));
}
}
}

首先,我们注意到我们要创建的新控件MySrvImageButton继承自ASP.NET服务器控件ImageButton,并继承了接口IScriptControl。

有了前面的理论描述,在此,我们省略对于其中几个常规方法的描述。

接下来,我们要构建这个控件库(即程序集)。右击此库工程,并点击“生成”命令以生成程序集AjaxImageButtonLib.dll,此库文件中即包含了我们的服务器控件。

(三)创建客户端JavaScript代码

这里创建的客户端控件类相应的JavaScript文件ImageButton.js在内容上完全相同,只不过为了区别起见,我们进行了某些地方的重新命名罢了。在此不再赘述。

(四)在示例网页中应用构建的新控件

以鼠标右击前面网站AjaxServCtrlTest,把它设置为“启动项目”。事实上,因为这个网站工程与前面的类库工程创建于同一个方案下,所以,在前面生成程序集AjaxImageButtonLib.dll的一结束,新建的服务器控件就被自动添加到Visual Studio 2005工具栏中,如下图3所示。于是,我们可以直接把这个控件拖动到示例网页Default.aspx中。

图3:拖动新建的服务器控件到示例网页中

根据前面的控件代码实现,不出所料,点击上图3中的图形按钮控件,即可在其相应的“属性”对话框中设置这个控件的hoverImageUrl属性,而且指定其ClientClickFunction方法(其实正是此控件的click事件处理器函数指针)。

注意,因为控件代码中的方法GetScriptReferences已经为我们自动生成了前面提到的$create方法,所以我们不需要再在ScriptManager中注册在本篇中创建的JavaScript文件—SrvImageButton.js了。

(五)运行及性能简析

现在,请按F5键运行此页面并移动鼠标到图像按钮上观察,你会注意到结果与上篇中的效果一致(即在鼠标移动切换新图像时,这些动作都发生于客户浏览器端而不再与服务器端相关)。下图4相应于此示例页面运行时刻快照。

图4:示例网页运行时刻屏幕快照

通过以鼠标右击网页并选择弹出菜单中的“查看源文件”观察上、下篇中示例页面相应的源码,我们会注意到其内容基本是一致的。另外,通过使用Fiddler观察这两个示例页面下载到客户端时各模块的大小,你也会注意到基本一致,如下面图5所示。

图5:两个示例页面下载到客户端时各模块大小比较

因为本文两个例子极为简单,所以其性能基本平衡。但随着服务端编程的复杂化,本篇中基于服务器端控件的扩展方案应该有较大的性能损耗。但应该仍具有令人满意的效果,这也正是AJAX Control Toolkit控件数量急剧增加的重要原因之一。而上篇中的方案基于“纯粹”(相对而言)的客户端,即使性能上与本篇中方案相差无几,但是却明显多出了跨越服务器端平台的优势,这也正是上篇中方案吸引人的主要原因。

六、总结

虽然以上、下两个篇幅形成此文,但是这也仅能通过简短的例子向你阐述了开发ASP.NETAJAX框架中的Ajax控件所涉及的主要技术。尽管目前的ASP.NET AJAX框架已经形成正规的1.0版本,而且这个框架为基于AJAX技术开发以ASP.NET 2.0为主的Web应用提供了全方位支持,但是这个框架仍然在许多方面有待改进。事实上,我们可以进一步沿着ASP.NET AJAX客户端与服务器端架构层次关系图进一步扩展其底层。当然,在此框架与Visual Studio整合方面也存在相当的挖掘潜力。

如今,随着微软Silverlight技术的推出,ASP.NET AJAX框架的重要性日显突出。自然,与此框架相关的控件开发也必将在这一大环境中占居着重要的位置。