日期:2010-10-29  浏览次数:20629 次

  摘要:在本系列文章中,我们首先讨论使用ASP.NET 2.0构建一个标准ListBox控件的增强版本(EnhancedListBox)。这个控件能够对它的项进行重排序,并且能够实现客户端与服务器端的同步功能。然后,我们把这样的两个控件组合起来创建一个复合控件(ListMover)。

  一、 引言

  构建提供丰富的客户端接口的复杂Web控件经常需要把一些客户端JavaScript代码与控件的服务器端代码集成到一起。然而,在一些情况下,为了达到某种巧妙的效果而把问题搞得过于复杂经常会破坏控件的内部服务器代码与生成的客户端HTML代码之间的数据同步,而当进行页面回寄时这将成为一个问题。在本文中,我将首先构建两个“很酷”的Web控件(都极容易导致这一问题),然后向你展示如何来修改这一“脆弱性”。

  本文中,我们将使用C#+ASP.NET 2.0来定制这些控件,并在后面向你简短介绍如何使之工作在ASP.NET 1.1(或1.0)环境中。

  当前,HTML仍然保持为Web应用程序生成阶段的主要语言。遗憾的是,它所使用的协议是无状态的,所以必须由Web开发者自己来处理这种无状态特点。通过使用一些架构特征,例如回寄机制和ViewState变量,ASP.NET有助于处理这个问题。然而,为了实现某些功能,还需要再作努力,从而借助于回寄事件把Web页面不断向服务器发出请求的各种技术结合起来。

  具体地说,我将分析如何使用JavaScript和DHTML存取在客户端生成的元素。其实,把客户端和服务器功能融合到一起要求使用大量的技巧才能达到最佳用户体验效果,而微软在其ASP.NET校验控件中就实现了这一点。为了提供一种丰富的客户端校验效果,该控件中使用了大量的JavaScript。

  作者注:本文假定你对定制Web控件开发有一个基本了解。因此,我将不再重复Web控件开发的基础内容,例如属性工作原理与风格的添加方式。

  二、 一种常规实现方法

  下面,我想向你展示如何构建一组很酷的控件,它们具有你在商业控件中才能看到的优秀功能。稍后,我将继续展示定制Web控件带给Web编程的完全封装优点。既然你已经了解如何开发定制Web控件,那么你应该知道的一个概念是封装一个控件所有的功能和行为(就象你在一个标准业务对象中所实现的那样)。在学习构建具有复杂行为的控件时,这种封装将极有用处。

  在第一个控件中,我将向你展示如何构建一个称为EnhancedListBox的控件。这个控件将扩展ASP.NET的ListBox控件—添加一个头部和一些重排序按钮。注意,这是一个直接继承自常规ListBox的控件。
之后,我还将向你展示如何构建一个复合控件—ListMover,它将包含两个上面提到的EnhancedListBox控件。这个ListMover控件还包含一些允许你从一个列表到另一个列表中移动项的按钮。

  其实,用常规方法(非面向Web控件的)来实现这种ASP.NET功能也并不困难。首先,你要把一个常规ListBox控件拖动到你的Web表单上并且使用一些数据填充它。

  然后,再添加一个标签用作标题,还有一组按钮用作重排序按钮。捕获这些按钮的服务器端事件是ASP.NET中的标准操作;因此,你只需要使用一种方法来取得当前选定的项并且根据用户点击的按钮从而把它放到该列表中的更高或更低的位置即可。例如,你可能编写如下的代码实现移动列表中的一项。

i_Index = ListBox1.SelectedIndex;
o_Item = ListBox1.SelectedItem;
ListBox1.Items.RemoveAt(this.SelectedIndex);
i_Index--;
if(i_Index < 0) i_Index = 0;
ListBox1.Items.Insert(i_Index, o_Item);


  下面,让我进行简单的分析。首先,我保存了当前的列表中选定项的索引值与当前项。然后,我在当前位置删除该项;之后,在一个较低位置(上一个索引值减1)重新插入该项。这里的逻辑非常简单,那么为什么我还要说明这个问题呢?

  借助于这种常规的ASP.NET编程方法,Web表单上面的重排序按钮将会引发一个实现ListBox中重排序的服务器端事件。这是由一个到服务器的回寄触发的;因此,这个回寄可能是一次“繁重的”往返,具体要信赖于表单上的具体内容及因特网速度。

  然而,因为这一代码实现的是一个标准ASP.NET回寄过程,所以由ASP.NET使用它的ViewState机制来负责状态处理。当再次生成页面时,列表框内容按要求的顺序正确生成。

  当然,你也可以使用与此相同的常规方式在ListMover控件中重新创建这个功能。篇幅所限,我在此省略,只好留待读者您来实现。这个Web表单上包含一对ListBox,还有一些指示从左向右或从右向左移动的按钮。这些按钮的服务器端事件将从一个ListBox中提取选择的项,然后把它添加到另一个列表中;反之亦然。如在刚才的例子中所展示的,ViewState在此能够完好工作以保持这两个ListBox中的项。

  三、 目的

  下面,我想向你展示如何把刚才描述的这些例子中所用的单个控件放到一个Web表单上。你可能猜出,我将向你展示如何把这两个例子中的功能封装到它们自己的一个Web控件中。借助于与在常规方法示例中描述的相同的服务器端事件模型,我们可以把所有的行为封装到每一个控件中来实现必要的功能。既然每一个控件都能够控制它自己的状态,那么包含它们的Web表单不必要做任何额外的工作。

  到目前为止,一切顺利。你可能问:“问题在哪里?”很好,假定页面开发者在含有大量内容的页面上使用这两个控件,而且每当发生一次重排序或移动,都需要到服务器端的重回寄时,这显然不是一个高效的Web站点要实现的。这正是使用一些JavaScript的原因。

  在本例中,你要使用JavaScript代码来存取EnhancedListBox控件中ListBox的内容以便在客户端进行重排序。

  在ListMover控件中,JavaScript代码将把项从一个列表移动到另一个列表。其最终结果是一样的,但是不需要进行服务器来回传送,因为不需要触发任何回寄。这样以来,你就可以解决即时响应和不需要回馈的问题。

  四、 问题

  ASP.NET在服务器端生成内容与在客户端生成内容之间有明显的界定。事实上,大部分情况下,这两部分没有关系;因此,问题出现了。其实,一个Web控件只是一个服务器端组件,它负责把HTML生成到浏览器端。的确,标准ASP.NET ListBox控件正是以HTML形式生成一个ListBox(作为一个<select>标签)。

  在<select>标签中的<option>子标签可以使用ListBox控件中的Item属性的内容来创建。Item属性在服务器端被填充,而其内容有助于在生成期间构建适当的HTML。这非常类似于生成一个<input>标签的文本框Web控件,而它的Text属性映射到<input>标签的Value属性。每当触发一个到服务器的页面回寄时,ListBox控件的Item属性都被保存到ViewState中,并且在重新生成页面前从ViewState中进行重建。

  在EnhancedListBox中进行重排序或在服务器端的ListMover中移动项都非常直接,并且允许支持正常的内置的ViewState机制而不需要我们作任何干扰。但是,当你使用客户端JavaScript添加这一能力来实现它们的功能时,它将破坏ViewState。这些控件并不再转回到服务器端,所以Item集合属性永远不会被保存以便在重新生成时被重载。代之的是,直接在HTML级别上存取生成的<select>标签中的<option>项。你可以借助JavaScript代码移动或重排序控件项;但是,当在页面上再次发生回寄时,你猜发生了什么?在移动(或重排序)开始前,控件的列表项就恢复它们的状态。

  我说过,如果功能发生在回寄期间的服务器端,那么,ViewState被保存并且被良好重载,从而使Item集合正确填充。但是,既然你的最终目标是在客户端实现这个功能,那么你就不再需要重新调整Item属性的内容,而是由你依赖的这个属性负责状态存储。现在,你可能会为难了。但是别担心—我有一个解决方案。现在,让我们开始使用必要的客户端脚本代码来开发该控件来实现每一个子控件所需要的功能。然后,我将向你展示如何使它与服务器代码保持重新同步。

  五、 EnhancedListBox控件

  在这个控件中,你要把两部分