讲座内容
这次我选择的讲座内容是最近在TechEd 2006 Europe中Shawn Burke的讲座“ASP.NET AJAX Control Toolkit Unleashed: Creating Rich Client-Side Controls and Components”。Shawn Burke是微软.NET Developer Platform总监。这个讲座的PPT和演示代码的下载地址已经由他本人公布在他的Blog上(本地下载)。另外,在MSDN's Showtime上也已经有了这个讲座的完整视频。
此次讲座的内容主要是对于ASP.NET AJAX Control Toolkit进行简单的介绍,展示了Extender控件是如何帮助ASP.NET开发人员简单地将丰富的用户体验集成到他们的Web应用程序中。在这次讲座里将看到应该如何在您的应用中使用ASP.NET AJAX Control Toolkit中的组件,并且了解开发人员是如何方便地开发一个APS.NET AJAX Extender的。
此次讲座分为两部分:“ASP.NET AJAX Control Toolkit介绍和使用”以及“开发一个Extender控件”。本文将对于该讲座的第二部分进行讲述,并且对其第二个演示的第一个部分进行分析。
那么现在我们来看一下如果使用ASP.NET AJAX Control Toolkit的功能进行组件开发。ASP.NET AJAX Control Toolkit对于组件开发也有相当好的支持,我们设计时就是专注于它应该如何能让AJAX组件的开发变得简单。在开发Toolkit中的组件时,大量的代码已经被填充到了一个模版中,开发人员可以专注于实现组件的功能。而对于组件开发,ASP.NET AJAX Control Toolkit也已经提供了许多的基础功能和特性。它能使开发人员专注于使用ASP.NET AJAX技术和各种良好实践,并且能够结合ASP.NET AJAX和Toolkit来开发出强大的AJAX组件,为此也不用去接受各种各样的其它概念。使用ASP.NET AJAX Control Toolkit开发组件的另一个重要的优势就是能够得到各种各样的支持,例如论坛,社区和完全开放的源代码等等。
在这里需要在提一下Behavior和Extender,Behavior是一个由JavaScript编写的客户端组件,它的目的是通过修改页面的HTML DOM,为客户端的元素添加一些或者修改一些功能。而Extender都是ASP.NET的组件,他将ASP.NET的控件和Behavior联系了起来,并且提供了服务器端以及设计期的支持,而Extender自己没有具有非常多的功能。
Bahavior是使用ASP.NET AJAX Library这个JavaScript框架编写的。ASP.NET AJAX Library是一个使用JavaScript开发的面向对象框架,它有一个能够自动提供跨浏览器能力的兼容层,这样我们就不需要为了各种浏览器写一些特殊的代码。另外它有自己的一套类型系统,也提供了调用Web Service方法的类,以及一些基本的DOM操作。当然它能够进行结构化的编程,也能提供良好封装性,也就是说,它虽然还是JavaScript,依旧不是类型安全的,依旧难以调试,但是使用ASP.NET AJAX Library进行开发能够比较好的将JavaScript代码进行管理。因此使用ASP.NET AJAX Library进行客户端的JavaScript开发,能够有效地使您的站点避免出现混乱不堪的JavaScript代码。
如果需要开发一个Toolkit组件,您可以有两种选择:一种是使用Extender+Behavior的组合方式,另一种就是开发ASP.NET AJAX-awared控件,也就是ScriptControl。那么应该如何选择以那种形式开发Toolkit组件呢?一般来说,如果所需的HTML行为比较特殊,或者对于您的站点非常有针对性,那么写一个控件并且将一个Behavior附加在上面。如果HTML的样式很普通,需要开发一个能够在一个普遍存在环境中使用的组件,那么就编写一个Extender。
那么应该如何开发一个控件,之前提到的那些组件又是如何一起工作的呢?我们现在来对这些组件解剖一下。
首先要编写的是一个Extender类。这是一个使用Visual Basic或C#等.NET Framework语言编写的类,我们为这两种语言都提供了模版。下面就是用于扩展一个组件最主要的服务器端代码:
[ClientScript("...")]
[TargetControlType(typeof(Control))]
public class MyExtender : ExtenderControlBase
{
[ExtenderControlProperty]
public string MyStringProp{}
[ExtenderControlProperty]
public int MyIntProp{}
}
上面是用于开发一个扩展的代码片断,首先我们需要定义一个继承于Toolkit里的ExtenderControlBase类的Extender类。它具有一些属性,可以使用户在服务器端通过代码来设置客户端Behavior的属性。所以在我们之前的例子里,我们只是在操作Extender。
在Extender之后则是Behavior,Behavior是使用JavaScript写的类,它以ASP.NET AJAX客户端组件的形式出现:
MyProject.MyBehavior = function(e){
MyProject.MyBehavior.initializeBase(this, [e]);
this._myStringPropValue = null;
this._myStringIntValue = 0;
}
MyProject.MyBehavior.prototype = {
initialize : function() {...},
get_MyStringProp : function(){...},
set_MyStringProp : function(value){...},
get_MyIntProp : function(){...},
set_MyIntProp : function(value){...}
}
可以发现,在这里,Behavior里定义的这些属性和Extender的属性一一对应,您可以发现这些对应的属性名是相同的的。
然后在使用Extender时,您可以在页面中发现这样的标记:
<cc1:MyExtender runat="server" TargetControlID="TextBox1"
MyStringProp="Hello" MyIntProp="23"
</cc1:MyExtender>
在上面的标记里我们在页面中定义了一个Extender控件,设置它的TargetControlID属性,这样就指定了这个Extender会扩展的控件,然后在指定其它一些属性。就是使用ASP.NET控件的标准方法。当然,您也可以通过编写代码来动态地使用Extender,就像您使用其它ASP.NET控件的方法一样。如下:
MyExtender ex1 = new MyExtender();
ex1.MyStringProp = "Hello";
ex1.MyIntProp = 23;
ex1.TargetControlID = "TextBox1";
Page.Add(ex1);
讲座演示
这个演示的代码可以在压缩包“TechEd_Dev344.zip”的“Demos\CreatingExtender\Finished”目录下找到,打开FontSizeExtender.sln即可。没有任何复杂的部署方式,只需要装有ASP.NET AJAX Beta 2。
现在来看一下我们即将演示的内容,如图(点击小图可查看大图):
我记得在CNN.com网站上它有个Widght,它可以让用户改变一片文章的字体大小。您可以不改变浏览器的字体大小,而单独改变某一块区域的字体大小。所以我想这应该会是个有趣的示例。这个Extender的TargetControl是一个含有文字的Element,我们会改变它的字体大小,例如上图中的红色部分。上图的黄色区域就是控制文字大小控件,蓝色为“加大”按钮,绿色为“减少”按钮。
如果想要开发Extender控件,首先需要建立一个Extender项目。在Toolkit的zip包内有一个VSI文件,安装后就会出现一些模版可以选择。我们可以使用其中的控件创建一个使用Extender的ASP.NET AJAX站点,您也可以使用其中的模版创建一个C#或者VB.NET的Extender组件项目。如图(点击小图可显示大图):
在这之前您需要安装ASP.NET AJAX,由于需要在GAC里查找所需的程序集。
首先我们来看一下Toolkit中的“序列化”能力元数据。这些功能的作用是使的这些组件能够对自身进行标记和描述,例如您可以为控件添加普通的属性,再使用CustomAttribute进行标记,这就告诉了Toolkit这些属性的意义,Toolkit会使用反射来获得它们。最常用的CustomAttribute以及它们的作用如下:
-
ExtenderControlPropertyAttribute:在之前的代码中您也可以看到这个CustromAttribute的使用。它的作用是告诉Toolkit,一个Extender的哪些属性需要被设置到客户端的Behavior中。
-
ElementReferenceAttribute:它表明了该属性会指定一个客户端的元素,这样只要在服务器端指定ID后,在客户端使用Behavior时就能直接得到那个元素了。
-
IDReferenceAttribute:它的作用是在服务器端指定一个服务器控件的ID,这样在客户端的Behavior就能得到这个服务器控件的ClientID,如果您查看页面的代码时则会看到一个可能会非常长的ID。
-
ClientPropertyNameAttribute:它是配合ExtenderControlPropertyAttribute使用的,能够把服务器端的属性名客户端Behavior里的不同属性名进行映射,而不是使用默认的相同属性名。
那么我们现在就来尝试着开发这个控件(老赵:请注意,在Shawn提供的压缩包内有三个空文件夹:Demo_Base、Demo_Step_2和Demo_Step_3,现在它们在演示时也是有内容的,我已经根据讲座视频设法将它们恢复到了演示时的样子,请点击这里下载)。
Step 1:Define the Extender Class
打开Demo_Base文件夹下的解决方案文件,可以看到这里已经存在了一个站点。事实上这个站点已经写好了,它就像是一张报纸。如图(点击小图可查看大图):
在这里您可以看到一些AJAX Control Toolkit的一些很有趣的新闻,比如它是如何能够轻易地添加客户端UI功能等等。这里存在许多文本,我们希望提供改变大小的能力。
我们要做的第一件事是创建我们的Toolkit项目,我们在安装Tookit的VSI文件后会产生Toolkit项目的模版,我们创建一个名为FontSize的Toolkit项目。如图(点击小图可查看大图):
点击确定以后可以看到它为我们创建了三个文件:FontSizeBehavior.js,FontSizeDesigner.cs和FontSizeExtender.cs文件。我们不用关心FontSizeDesigner类,Toolkit会自动为你提供设计器的功能,您在大多数情况下无须为它编写代码。最重要的是Behavior和Extender。
我们首先为我们的组件建立Extender。我们首先需要关心的元数据是TargetControlTypeAttribute的使用,它表示了您的Extender能够扩展的控件类型。您可以指定任何的ASP.NET控件,在这里我们将其限制为Paenl,如下:
[Designer(typeof(FontSizeDesigner))]
[ClientScriptResource("FontSize.FontSizeBehavior", "FontSize.FontSizeBehavior.js")]
[TargetControlType(typeof(Panel))]
public class FontSizeExtender : ExtenderControlBase {
...
}
接下来为其添加Property代码:
[ExtenderControlProperty]
public string SizePanelControlID
{
get {
return GetPropertyStringValue("SizePanelControlID");
}
set {
SetPropertyStringValue("SizePanelControlID", value);
}
}
[ExtenderControlProperty]
public string IncreaseSizeControlID { ... }
[ExtenderControlProperty]
public string DecreaseSizeControlID { ... }
[ExtenderControlProperty]
public string PanelHoverCssClass { ... }
[DefaultValue(2)]
[ExtenderControlProperty]
public int FontSizeIncrement
{
get {
return GetPropertyValue("FontSizeIncrement", 2);
}
set {
SetPropertyIntValue("FontSizeIncrement", value);
}
}
第一个是SizePanelControlID,就是我们用于放置增大缩小按钮的控件。IncreaseSizeControlID和DecreaseSizeControlID的作用是指定了哪个控件被点击时我们会增大或减小Panel的字体。PanelHoverCssClass是我们定义的CSS类,当我们将鼠标移到Panel上时会改变Panel的CSS类。FontSizeIncrement表示了每次我们点击按钮时字体大小的改变程度。
请注意,我们这里存在一些有用的方法:GetPropertyStringValue、SetPropertyStringValue、GetPropertyInt等,Toolkit中定义了一整套这样的方法,Toolkit为自己会对这些属性的保存方式作一些特别的处理,因此请不要使用自己的方法,例如ViewState来保存。
这里还出现了DefaultValueAttribute,我会在稍后对它进行介绍。
接着在WebSite项目中引入这个FontSize项目,编译,然后在页面中添加如下的注册标记:
<%@ Register Assembly="FontSize" Namespace="FontSize" TagPrefix="fontSize" %>
然后我们需要修改FontSizeBehavior.js文件。首先我们需要在构造函数里添加我们需要的私有变量。如下:
FontSize.FontSizeBehavior = function(element) {
FontSize.FontSizeBehavior.initializeBase(this, [element]);
// the DOM elemens we'll be interacting with
//
this._sizePanelElement = null;
this._incrementElement = null;
this._decrementElement = null;
this._hoverCssClass = null;
}
接着在客户端里添加一个Behavior的属性:
get_sizePanelElement : function() {
return this._sizePanelElement;
},
set_sizePanelElement: function(val) {
this._sizePanelElement = val;
}
在客户端是使用get/set方法来表示一个属性的。至于客户端组件的创建,赋值,销毁等操作都是由ASP.NET AJAX来处理了。
我们打开页面,在页面里添加我们的Extender以及一组用于字体放大/缩小的按钮:
...
<fontSize:FontSizeExtender ID="fse" runat="server"
TargetControlID="pnlMainStory" SizePanelControlID="extenderUI" />
<asp:Panel ID="extenderUI" runat="server" CssClass="popup">
<asp:Button ID="btnSmaller" runat="server" Text="-" />
<asp:Button ID="btnBigger" runat="server" Text="+" />
</asp:Panel>
<div class="panelNormal">
...
<asp:Panel ID="pnlMainStory" runat="server">
...
</div>
...
在这里我们将TargetControlID设为pnlMainStory,表明Extender将操作那个Panel。SizePanelControlID设为extenderUI,它为增大/减小按钮的容器。然后指定分别指定字体增大和减小两个按钮,最后将CssClass设为panelHover。现在我们打开页面,由于我们只写了一小部分代码,因此会出现脚本执行错误。在页面下方可以发现这段代码:
Sys.Application.add_init(function() {
$create(
FontSize.FontSizeBehavior,
{"DecreaseSizeControlID":"", "id":"fse", "IncreaseSizeControlID":"",
"PanelHoverCssClass":"", "SizePanelControlID":"extenderUI"},
null, null, $get('pnlMainStory'));
});
$create方法是ASP.NET AJAX Library中定义的方法,用于创建一个组件。这就是ASP.NET AJAX将客户端组件与DOM元素进行绑定的方法,在$create方法内部会有许多操作。它被添加到Sys.Application的init事件中,这个事件会在脚本加载完成,在页面在初始化时触发。$create的第一个参数表示即将创建的Behavior类型,从第二个参数中可以看到我们定义的属性,它们会被赋值,可以发现这里大部分的属性都是空值。
我们暂时不关心客户端没有定义的属性,事实上就SizePanelControlID来说就有很多问题。第一个问题就在于,我们定义了SizePanelControlID的ID为extenderUI,但是如果这个控件被放进了别的控件内,那么很可能它就会变成一个有许多前缀的ID。至于第二个问题,请注意在Behavior中我们希望sizePanelElement属性会直接获得一个HTML元素,而不是元素的ID。让我们先来解决这两个问题。另外可以发现,我们需要的客户端属性名称为sizePanelElement,而不是Extender的属性名:SizePanelControlID。
正如之前提到过的,我们会在Extender中大量使用CustomAttribute。如下:
[ExtenderControlProperty]
[IDReferenceProperty(typeof(Panel))]
[ElementReference]
[ClientPropertyName("sizePanelElement")]
public string SizePanelControlID {
get {
return GetPropertyStringValue("SizePanelControlID");
}
set {
SetPropertyStringValue("SizePanelControlID", value);
}
}
首先我会使用IDReferencePropertyAttribute,它告诉Toolkit这个字符串属性事实上代表了一个ASP.NET服务器端的Panel控件。这里起到的效果有两个:首先它能告诉设计器,这样设计器在处理这个属性时就会使用一个下拉框列出所有的Panel让用户选择;其次它保证了最终传递给客户端的值是控件在客户端的ID,而不是在服务器端的ID,这样客户端就能通过这个客户端ID来得到这个元素。在这里我们添加的另一个CustomAttribute是ElementReferenceAttribute,它的作用就是告诉Toolkit需要在客户端得到指定的HTML元素后再将值赋给属性。最后我们使用了ClientPropertyNameAttribute,告诉Toolkit需要在客户端使用sizePanelElement作为属性名。
我们编译项目,刷新页面,再来查看一下页面的代码。如下:
Sys.Application.add_init(function() {
$create(
FontSize.FontSizeBehavior,
{"DecreaseSizeControlID" : "", "id" : "fse",
"IncreaseSizeControlID" : "", "PanelHoverCssClass" : "",
"sizePanelElement" : $get("extenderUI")},
null, null, $get('pnlMainStory'));
});
可以发现,由于ElementReferenceAttribute的作用,这里的代码使用了客户端$get方法,这是ASP.NET AJAX为document.getElementById方法建立的缩写。另外可以发现属性名也从SizePanelControlID变为了sizePanelElement,这是因为使用了ClientPropertyNameAttribute。可惜由于这个控件没有在别的控件之内,因此客户端ID和服务器端ID相同,依旧是extenderUI,没有看出IDReferencePropertyAttribute的效果。
于是我们为其余的服务器端属性也添加CustomAttribute(老赵:这里就不一一例举了,大家可以查看代码)。这里还使用了DefalutValueAttribute。它的作用是告诉Toolkit,如果没有对属性进行修改,那么会使用哪个值作为默认值,这样在当前值等于默认值的时候则会省去不少客户端代码。
由于没有提供默认值,刚才生成的客户端代码依旧有很多空字符串,我们重新编译项目,刷新页面,再看一下现在代码:
Sys.Application.add_init(function() {
$create(
FontSize.FontSizeBehavior,
{"id":"fse","sizePanelElement":$get('extenderUI')},
null, null, $get('pnlMainStory'));
});
可以发现,许多属性的设置被省去了,这就是DefaultValue的效果。
于是我们在客户端也添加上相应的Behavior属性(老赵:这里就不一一例举了,大家可以查看代码),最后补全页面中的Extender声明:
<fontSize:FontSizeExtender ID="fse" runat="server" TargetControlID="pnlMainStory"
SizePanelControlID="extenderUI" DecreaseSizeControlID="btnSmaller"
IncreaseSizeControlID="btnBigger" PanelHoverCssClass="panelHover" />
我们设置了所有的属性:指定了用于增大/减小字体的按钮,还有鼠标移上Panel时所使用的CSS类。我们再来看一下现在的客户端代码:
Sys.Application.add_init(function() {
$create(
FontSize.FontSizeBehavior,
{"decrementElement" : $get('btnSmaller'),
"hoverCssClass" : "panelHover", "id" : "fse",
"incrementElement" : $get('btnBigger'),
"sizePanelElement" : $get('extenderUI')},
null, null, $get('pnlMainStory'));
});