1.
微软ASP.NET 2.0 AJAX Extensions,作为ASP.NET 2.0 AJAX 1.0框架组成的最基本部分,为扩展ASP.NET 2.0服务器端控件提供了支持。借助于这个新框架,你可以使用JavaScript,DHTML以及Web浏览器提供的AJAX能力实现更为丰富的可视化效果。
本文中,我们将探讨如何创建一个使用ASP.NET 2.0 AJAX扩展特征的ASP.NET Web服务器控件以扩展浏览器端功能。然后,我们可以使用一个客户端控件实现把这种新功能添加到客户端文档对象模型(DOM)元素中。注意,这个客户端控件是通过实现服务器控件提供的IScriptControl接口来建立与一个服务器控件的关联。
具体来说,我们要讨论:
◆如何创建一个定制Web服务器控件,它能够封装客户端行为以及用户可以用于设置并控制该行为的属性;
◆如何创建一个与该Web服务器控件相关联的客户端控件;
◆如何在客户端控件中处理浏览器DOM事件。
【注意】除本文介绍的方法外,新框架中还提供了通过创建一个扩展(extender)控件—它能够把客户端能力封装到一个行为中,然后把它依附到一个Web服务器控件—来扩展Web服务器控件(添加丰富的客户端功能)的功能。因为一个扩展控件并不是它的关联控件中的一部分,所以,你可以创建单个的可以与若干类型的Web服务器控件相关联的扩展控件。本文中,我们不讨论这种技术。
二、客户端需求分析
把客户端行为添加到一个Web服务器控件的第一步是,决定该控件在浏览器中将提供什么行为。然后,我们还要确定为了实现该行为必须提供的客户端功能。
在本文中创建的示例Web服务器控件实现一种简单的客户端行为。当在浏览器中选择(拥有焦点)它时,该控件(一个TextBox控件)被高度显示。例如,当它拥有焦点时,该控件可能改变背景颜色,而当焦点移动到另一个控件时还原为缺省的颜色。
为了实现这一行为,本文中的客户端控件必须实现下列功能:
①提供一种实现高亮DOM元素的方法。
为了高亮一个ASP.NET Web页面中的DOM元素,本文中的客户端控件将应用一种层叠式样表(CSS)风格—我们通过一个类来提供。此外,这个风格是用户可配置的。
②实现一种方法以便把DOM元素恢复到正常状态。
也是通过应用一种层叠式样表(CSS)风格来从一个ASP.NET Web页面元素中删除其高亮状态,而且此风格也是用户可配置的。
③标识何时选择一个DOM元素。
为了标识何时选择一个DOM元素(拥有焦点),该控件需要处理对应DOM元素的onfocus事件。
④标识何时取消选择一个DOM元素。
为此,该控件需要处理DOM元素的onblur事件。
三、创建Web服务器控件
从基本操作思路及形式上看,一个使用ASP.NET 2.0 AJAX扩展技术包括客户端特征的Web服务器控件类似于任何其它Web服务器控件。然而,该控件还必须实现System.Web.UI命名空间中的IScriptControl接口。在本例中,我们将通过继承TextBox类并实现IScriptControl接口来扩展ASP.NET TextBox服务器控件。
启动Visual Studio 2005,选择“文件→新建网站…”,然后选择“ASP.NET AJAX-Enabled Web Site”模板,命名工程为“AjaxEnhancedServCtrl”,并选择C#作为编程语言,最后点击OK。
然后,右击解决方案资源管理器添加一个新类,并命名为“EnhancedTextBox”(见下图1)。
图1:通过创建一个新类创建一个扩展的Web服务器控件 |
然后,打开文件EnhancedTextBox.cs,并作如下修改:
|
至此,我们可以借助于VS2005提供的友好的“类关系图”编辑器来更为直观地为此类添加字段、属性和方法等。这只要在文件EnhancedTextBox.cs上右击弹出菜单中的“查看类关系图”来切换到类关系图编辑器。最后编辑完成的类关系图如下图2所示。
图2:类EnhancedTextBox的类关系图 |
【注意】通过试验,我发现无法通过类关系图在类上继承接口IScriptControl的有关成员。不知什么缘故。
下面让我们进行具体分析。
这个新控件提供了两个用于实现客户端要求的属性:
◆HighlightCssClass—在控件取得焦点时,用来高亮DOM元素的CSS类;
◆NoHighlightCssClass—在控件失去焦点时,应用于DOM元素的CSS类。
2.
实现IScriptControl接口
下列是你必须在一个Web服务器控件中实现的IScriptControl接口中的成员:
①GetScriptDescriptors()
此方法返回一个ScriptDescriptor对象(此对象提供了有关与Web服务器控件一起使用的客户端组件实例的信息)集合。其中包括要创建的客户端类型,要指派的属性,以及要添加处理器的事件。
②GetScriptReferences()
此方法返回一个ScriptReference对象(此对象提供了关于与控件一起包括的客户端脚本库的信息)集合。这个客户端脚本库定义了客户端类型和相关的任何其它JavaScript代码。
本文中的Web服务器控件使用GetScriptDescriptors()方法来定义客户端控件类型的实例。该控件创建一个新的ScriptControlDescriptor对象(派生自ScriptDescriptor类的ScriptControlDescriptor子类)并且把它包括在GetScriptDescriptors()方法的返回值中。
ScriptControlDescriptor对象包括了客户端类(EnhancedTextBox)的名字和该Web服务器控件相应的ClientID值—这个值将用作生成的DOM元素的id值。该客户端类名和ClientID值将被传递给ScriptControlDescriptor对象相应的构造器。
ScriptControlDescriptor类用于设置客户端控件的属性值—这些值都来自于Web服务器控件的属性。为了定义客户端控件的属性,本文中的服务器控件使用了ScriptControlDescriptor类的AddProperty(String,Object)方法。然后,它为该客户端控件属性指定一个名和值(基于Web服务器控件相应的属性)。这里使用一个ScriptControlDescriptor对象来设置客户端控件的highlightCssClass和nohighlightCssClass属性的值。
该控件在GetScriptDescriptors()方法的返回值中提供了这个ScriptControlDescriptor对象。因此,无论何时把此Web服务器控件生成到浏览器端,ASP.NET AJAX都会相应地生成新的JavaScript—此脚本将创建一个带有所有的属性和事件处理器的客户端控件的实例。然后,根据从Web服务器控件生成的ClientID,此控件实例被依附到相应的DOM元素。下列代码展示了本文中的示例Web页面中的包括该Web服务器控件的声明性ASP.NET标记。
|
注意,该页面生成的输出包括一个对$create方法的调用。这个方法将标识出要创建的客户端类,同时还提供了与客户端控件相关联的DOM对象的客户端属性和id值。下面的代码展示了一个生成的$create方法。
|
本文中的Web服务器控件使用GetScriptReferences()方法传递定义客户端控件类型的脚本库的位置—在这个示例中,这相应于一个指向EnhancedTextBox.js脚本文件(后面将创建)的URL。该引用是通过创建一个新的ScriptReference对象实现的,然后,把包含客户端代码的文件的路径属性设置为URL。
下列示例展示了GetScriptDescriptors()和GetScriptReferences()方法的实现。
|
客户端控件的注册
必须使用当前页面中的ScriptManager对象来注册客户端控件。这是通过调用ScriptManager类的RegisterScriptControl()方法并提供一个到该客户端控件的引用实现的。
本文中的Web服务器控件使用页面中的ScriptManager控件把本身注册为一个客户端控件。为此,此控件重载了基类控件TextBox中的OnPreRender(EventArgs)方法。然后,它调用RegisterScriptControl()方法把本身注册为一个客户端控件。此外,该控件还注册了由GetScriptDescriptors()方法创建的脚本描述符—这是通过在控件的Render方法中调用RegisterScriptDescriptors()方法实现的。
下列代码展示了RegisterScriptControl()和RegisterScriptDescriptors()方法的使用(加粗部分)。
|
关于该Web服务器控件完整的代码请参考本文相应源码文件。
四、创建客户端控件
注意,在上面Web服务器控件中,GetScriptReferences()方法指定一个包含控件类型的客户端代码的JavaScript文件(EnhancedTextBox.js)。在此,我们要详细讨论该文件中的JavaScript代码。
右击“解决方案资源管理器”,选择菜单项“添加新项…”,创建一个JavaScript文件—EnhancedTextBox.js。在此脚本文件中,我们要定义一个客户端控件类,它将通过实现服务器控件提供的IScriptControl接口来建立与前面创建的服务器控件的关联。
注意,该客户端控件代码必须与由GetScriptDescriptors()方法返回的ScriptDescriptor对象中指定的成员相匹配。此外,这个客户端控件完全可以拥有与Web服务器控件类中的成员不相匹配的成员。
本文中的Web服务器控件把该客户端控件的名字设置为Samples.EnhancedTextBox,并且定义该客户端控件的两个属性—highlightCssClass和nohighlightCssClass。
注册客户端命名空间
首先,客户端控件代码必须调用Type类的registerNamespace方法来注册它的命名空间(“Samples”)。下列语句展示如何注册控件的命名空间。
|
定义客户端类
接下来,我们需要定义Samples.EnhancedTextBox类。这个类包括两个Web服务器控件提供的用于存储属性值的属性。它还包括两个指定DOM元素(关联于Samples.EnhancedTextBox控件)的onfocus和onblur事件处理器的事件委托。
下列示例展示了Samples.EnhancedTextBox类的定义。
|
定义类的Prototype(原型)
在定义Samples.EnhancedTextBox类之后,需要在客户端代码中定义该类的prototype。这个prototype中要包括属性的get和set访问器,以及事件处理器,还包括一个initialize方法(当创建该控件的一个实例时调用)和一个dispose方法(当页面中不再要求使用该控件时调用)。
定义DOM元素的事件处理器
一个客户端类的事件处理器被定义为类prototype中的方法。这些处理器关联于事件委托并且关联于浏览器DOM的事件—通过使用addHandlers方法实现(后面将讨论这个方法)。
下列片断展示了Samples.EnhancedTextBox控件的事件处理器方法。
|
定义属性get和set访问器
每个在服务器控件的GetScriptDescriptors()方法的ScriptDescriptor对象中标识的属性都必须拥有相应的客户端访问器。这些客户端属性访问器被定义为客户端类prototype中的get_<属性名>和set_<属性名>方法。
【注意】JavaScript是大小写敏感的。因此,get和set访问器名称必须准确匹配Web服务器控件的GetScriptDescriptors()方法中的ScriptDescriptor对象中标识的属性名称。
下列的示例展示了相应于Samples.EnhancedTextBox控件的get和set属性访问器。
|
4.
实现initialize和dispose方法
当创建该控件的一个实例时,需要调用initialize方法。在这个方法中,需要设置缺省的属性值,创建函数委托,并且把委托添加为事件处理器。
本文中Samples.EnhancedTextBox类的initialize方法完成如下任务:
①调用Sys.UI.Control基类的initialize方法;
②调用addHandlers方法以便把事件委托添加为相关的HTML元素()的onfocus和onblur事件的处理器。
当页面中不再使用该控件的一个实例并且将删除之时,调用dispose方法。使用这个方法可以释放该控件不再要求使用的任何资源—例如DOM事件处理器。
本文中Samples.EnhancedTextBox类的dispose方法完成如下任务:
◆调用clearHandlers方法来清除作为相关联的DOM元素的onfocus和onblur事件处理器的事件委托。
◆调用基类Control的dispose方法。
【注意】一个客户端类的dispose方法可能不止一次被调用。因此,在编写dispose方法的内容时应该注意这一点。
下列示例展示Samples.EnhancedTextBox prototype中initialize和dispose方法的实现。
|
注册控件
创建客户端控件的最后一项任务是通过调用registerClass方法注册客户端类。因为该类是一个客户端控件,对registerClass方法的调用需要包括要注册的JavaScript类名,还应指定Control为基类。
下列代码展示了Samples.EnhancedTextBox控件中对registerClass方法的调用。注意,后面还包括一个对Sys.Application对象的notifyScriptLoaded方法的调用。为了通知微软AJAX库JavaScript文件已经加载,必须调用这个方法。
|
下面是这个客户端控件—Samples.EnhancedTextBox的完整代码:
|
在此,我们看到了相对模块化和面向对象化的JavaScript编程。而且,在属性descriptor的定义中还使用了JSON表示方式,从而进一步简化了JavaScript编程。
据估计,ASP.NET 2.0 AJAX框架小组有可能在未来的版本中在上面的js编辑器中加入对JavaScript以及框架特有语法的智能感知支持;这样的话,一切将趋于完美.
5.
五、试验—在ASP.NET页面中应用客户端控件
在此,我们仅概括描述如何在一个ASP.NET Web页面中使用示例客户端控件的步骤:
1.创建一个新的ASP.NET Web页面。
2.如果该页面上没有一个ScriptManager控件,添加一个。
3.对于将以高亮显示和不以高亮显示的文本框分别创建风格规则。
4.把一个@register指令添加到页面上,然后指定该示例Web服务器控件的命名空间和TagPrefix属性。
5.通过使用标记把EnhancedTextBox控件的一个实例添加到页面。
6.在页面添加另一个控件,例如一个按钮控件。
7.把HighlightCssClass属性设置为highlight CSS风格,并把NoHighlightCssClass属性设置为no-highlight CSS风格。
8.运行该页面并且依次选择每一个控件。
下图3展示了示例Web程序的运行结果快照。
图3:示例Web程序的运行结果快照 |
请注意,当你选择三个EnhancedTextBox控件中的任何一个时,它以高亮显示;当焦点离开时,高亮显示恢复到原来。
六、小结
通过本文分析,我们可以看出,ASP.NET AJAX框架不仅仅是为基于ASP.NET 2.0的Web开发提供了一种客户端AJAX方案,开发人员还可以基于ASP.NET 2.0中现成的已经具有强大功能的服务器控件进一步创建自己具有更强功能的客户端组件,并应用于自己的Web开发中。
另外,上面分析中的第一步(创建Web服务器控件)相对比较容易,基本是公式化的;而第二步(创建客户端控件)则相对难度大一些,这一步中需要纯手工的JavaScript编程,只能依赖于新框架中提供的现成简单例子并进行自己的扩展,没有针对新框架的智能感知的JavaScript编辑器所用,而且需要开发人员对新框架以及对JavaScript编程的面向对象特征有较深入的理解。还好,这基本上算是基于ASP.NET AJAX框架进行开发中难度系数最大且需求量相对较少的部分,因此普通AJAX开发者是不必过于担心的。
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
namespace Samples.CS
{
public class SampleTextBox : TextBox, IScriptControl
{
private string _highlightCssClass;
private string _noHighlightCssClass;
private ScriptManager sm;
public string HighlightCssClass
{
get { return _highlightCssClass; }
set { _highlightCssClass = value; }
}
public string NoHighlightCssClass
{
get { return _noHighlightCssClass; }
set { _noHighlightCssClass = value; }
}
protected override void OnPreRender(EventArgs e)
{
if (!this.DesignMode)
{
// Test for ScriptManager and register if it exists
sm = ScriptManager.GetCurrent(Page);
if (sm == null)
throw new HttpException("A ScriptManager control must exist on the current page.");
sm.RegisterScriptControl(this);
}
base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter writer)
{
if (!this.DesignMode)
sm.RegisterScriptDescriptors(this);
base.Render(writer);
}
protected virtual IEnumerable<ScriptReference> GetScriptReferences()
{
ScriptReference reference = new ScriptReference();
reference.Path = ResolveClientUrl("SampleTextBox.js");
return new ScriptReference[] { reference };
}
protected virtual IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
ScriptControlDescriptor descriptor = new ScriptControlDescriptor("Samples.SampleTextBox", this.ClientID);
descriptor.AddProperty("highlightCssClass", this.HighlightCssClass);
descriptor.AddProperty("nohighlightCssClass", this.NoHighlightCssClass);
return new ScriptDescriptor[] { descriptor };
}
IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
{
return GetScriptReferences();
}
IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()
{
return GetScriptDescriptors();
}
}
}