使用 .NET 框架轻松开发完美的 Web 窗体控件(二)

翻译|其它|编辑:郝浩|2005-06-16 10:55:00.000|阅读 1462 次

概述:

# 界面/图表报表/文档/IDE等千款热门软控件火热销售中 >>



最后,我需要一个客户端来使用该控件,以便对其进行调试和显示。我向现有的解决方案添加了一个包含 ASP.NET 页的新 Web 应用程序。为了向工具箱中添加新的 Web 窗体控件,我右键单击并选择了 Customize Toolbox,调出了图 5 中所示的对话框。我浏览到了新的 Web 控件 DLL,选择了它,该控件即出现在控件列表中,如图 6 中所示。





图 6 控件列表中的新控件

之后,就可以将它放置到 ASPX 页上并设置其属性。生成项目并在浏览器中启动它时,控件的外观如图 2 所示。
 


更为复杂的示例
现在,我们已经介绍了 .NET Web 控件的基本功能,接下来,让我们看一个示例,该示例演示了 Web 窗体控件如何为页上的其他控件激发 .NET 事件。我发现 SDK 文档在介绍 Web 控件事件时很模糊。它将用户在浏览器上的单击(从而启动过程)、对它所触发的服务器的回发,以及收到回发的服务器端控件向页上的其他控件发送的通知全部都定义为事件。下面,我将尝试对这些事件之间的区别进行准确的说明。



我编写了一个更为复杂的示例控件,该控件显示在图 7 中的 ASPX 页上。它在用户的浏览器中显示一个表格,该表格的每个单元格中都显示自己的行号和列号。该控件公开称为 Rows 和 Columns 的属性。这两个属性都是整型数,在设计时设置。当用户单击表格中的任何单元格时,窗体将被回发到服务器。在服务器上汇编并初始化该页后,该表格控件将确定用户单击的是哪个单元格,并在服务器上向页上任何愿意侦听的其他控件激发一个 .NET 框架事件。在本例中,页中包含一个事件处理程序,该处理程序设置一个单独的标签控件的值,以显示用户单击的单元格的行号和列号。




图 7 表格控件


开发此控件时,我先是将 Render 方法编写为直接发出显示具有所需行数和列数的表格的 HTML,那非常简单。接着,我想要添加导致浏览器在用户单击表格中的单元格时将窗体回发到服务器的 HTML。这需要使用一些其他技巧(参见图 8)。

可以看到,每个表格数据项 () 都创建一个单元格,且都包含一个 onClick 属性,该属性调用一个客户端脚本,传递要处理回发的服务器端控件(本例中为表格控件)的 ID 以及一个包含任意参数的字符串。这里,该字符串为表格单元格的文本,这使得服务器端控件能够标识用户所单击的单元格,但该字符串可以是您所需要的任何内容。图片底部显示的客户端回发脚本将这些参数放置到隐藏的输入控件中,并执行对服务器的回发。

稍后我将讲解对这种回发的服务器端处理,让我们首先看看这些 HTML 是如何生成的。这看上去很繁琐,好消息是您无需自己编写它们。请记住,您的控件存在于 ASP.NET 页上,因此能够通过基础 Page 类成员变量访问该页的所有方法。方法 Page.GetPostBackEventReference 导致框架生成页上的 HTML 脚本,并返回调用它的 HTML 字符串。接着,我将该字符串添加到 元素的属性中,该元素能够使用前述的 HtmlTextWriter 方法。您可以在图 9 中看到 Render 方法的代码。请暂且忽略处理视图状态的代码部分;我将在下一节中对它们进行说明。)

如果浏览器不能运行 JScript 怎么办?我将在稍后进行讲解。现在,先假定浏览器能够运行 JScript,或者,我们明确声明我们仅关注浏览器能够运行 JScript 的情况。

回发窗体到达 ASP.NET,ASP.NET 在服务器上加载目标页,并创建它上面的控件。ASP.NET 需要将回发传递给它的目标控件。ASP.NET 是通过客户端脚本填充的隐藏输入控件得知该目标控件的。服务器端控件通过实现称为 IPostBackEventHandler 的接口并重写 RaisePostBackEvent 方法来接受该输入通知。我认为该方法名称非常容易引起误解。它不会引发回发事件;它通过 ASP.NET 从浏览器接受一个事件并引发一个服务器端 .NET 事件。该方法存在的唯一目的是将浏览器发送的通用窗体回发事件转换为具有有用参数的已命名且有意义的 .NET 控件事件,其他服务器端控件可以侦听该事件,页设计器可以方便地为该事件编写代码。如果将它看作 AcceptPostbackAndOptionallyRaiseServerEvent,那么您大脑中得到的就是正确的模型。

我在这个命名方式很差劲的方法中放入了一些代码,在控件从用户的浏览器接收回发时执行这些代码(参见图 10)。查明哪个控件应当接收回发后,ASP.NET 调用此方法并向它传递 eventArgument 字符串,该字符串是由客户端传递给客户端脚本然后在隐藏输入变量中传输的。本例中,该字符串为用户单击的表格单元格的文本。我的示例代码可以从字符串中分析出行号和列号,因此服务器端处理程序知道用户单击了哪个单元格。

如果已知您的控件是唯一一个关注谁导致了回发的控件,什么也不需要做;只需在 RaisePostBackEvent 方法中的适当位置编写处理程序代码即可。但是,您的控件在收到回发后所需要完成的主要任务之一是通知页上的其他控件您的控件上发生了某些事件。


为此,您的控件需要使用 Windows 窗体控件中使用的相同通用事件处理机制向页和其他服务器控件激发 .NET 事件。本例中,我向控件添加了一个称为 TableCellClicked 的事件,其中包含两个参数,即单击的行和列,如图 10 所示。可以为所需的任何控件安装处理程序,以接收该事件。在我的示例中,页中包含一个处理程序,接收来自表格控件的事件,并将所单击的单元格设置到标签控件中(参见图 11)

总之,Web 窗体控件中的事件处理包括两个必需的部分,和一个可选的第三部分。首先,控件的 Render 方法必须生成客户端 HTML,这些 HTML 在客户端发生您所关注的事件时导致对控件的回发。其次,您的控件必须实现 IPostBackEventHandler 接口,以便 ASP.NET 能够通知您的控件它收到了该回发,并传递有关它的附加信息。接下来是一个可选的部分,您的控件可以(很可能会选择)激发 .NET 事件,这样其他控件就可以接收所发生的这些事件的通知。


视图状态管理


Web 页本质上是无状态的。这么讲的意思是一个页上所显示的内容与用户以前所查看的内容无关,除非编写代码将它们关联起来。当用户只是查看静态文本页时,没有太大问题。但是,由于目前与 Web 站点的大多数交互都涉及跨越多个页的持续会话,这将造成严重的问题。SDK 文档声称您必须“向用户提供连续性的假相”。文档作者对问题的分析是完全过时的。用户体验是一切的核心,这是金科玉律。应当是通过您的代码来达到用户的预期,而不是其他方式。如果您的编程模型与用户的需求不符,您就必须编写代码来使其相符。用户的连续性是真实的,真正的假相是代码的连续性。

在 ASPX 页上编写代码的设计者能够访问 Session 和 Application 集合对象等功能,以维护一个页到另一个页的状态。但是控件设计者无法使用它们,因为她不知道页的会话状态何时因超时而被放弃,或者被页程序员显式转储。实际上,她甚至不知道是否已打开会话状态,并且无法在未打开会话状态时对其进行更改。因此,如果您的控件需要在对页的一次呈现到另一次呈现之间维护状态,需要采用其他方法。

.NET 框架提供了一种机制,使得 Web 窗体控件能够安全且方便地维护它们的状态。控件基类包含一个称为 ViewState 的成员。它是一个与 Session 和 Application 对象集合使用的属性包集合类型相同的属性包集合,不同之处在于它将自己的数据存储在页上的一个隐藏文本字段中。您可以在图8 中名为 __VIEWSTATE 的隐藏输入字段中看到视图状态。



当您的控件将数据置入视图状态集合中时,ASP.NET 将它序列化到 ViewState 字符串中,并将其作为已呈现的页的一部分传输到客户端。当页被回发到服务器时,ASP.NET 将提取隐藏的变量并将其反序列化到各个控件的 ViewState 成员变量。这种体系结构对于使用网络场时的可伸缩性尤其有利,这是因为它避免了任何类型的服务器关系。任何处理回发的计算机都可以看到上一次的状态,并为下一次存储它(可能是更新后的状态)。

我将我的示例表格控件编写为使用 ViewState 记忆其单击状态,即用户单击的单元格的行和列。在我的控件的构造函数中,可以看到我创建了用于记忆所单击的行和列的视图状态变量,并将它们设置为 -1,指示无选项。

' Start member variables in desired default state;
' no selected cell

Public Sub New()
Me.ViewState("SelectedRow") = -1
Me.ViewState("SelectedColumn") = -1
End Sub

从客户端收到单击回发时(参见图 10),我提取了用户所选择的单元格并将其存储到 ViewState 中。呈现时(参见图 9),我提取了选定的行和列,并调整 HTML 以正确地显示选定的单元格。这就是所有的步骤。很简单是吧?



请注意,基类包含一个称为 EnableViewState 的成员变量,它的说明指出它通知控件是否将其内部状态保存到 ViewState 中。但是,如果使用示例代码,将发现该变量看上去对其无任何效果。这是因为该变量没有在内部关闭 ViewState 机制。它只是一个 Boolean 标志,用于通知控件:对于页设计器来说,最好提前关闭 ViewState。是否编写代码以检查并响应该变量的状态全在乎您的选择,在本例中,我选择了这么做。看到有人违反了最少惊诧原则时多么惹人生厌了吗?所以千万别对自己的客户这么做,好吗?


客户端脚本

前面讲解过的大多数控件功能都发生在服务器端,事实上,许多人将这些 Web 窗体控件称为“服务器控件”,以便强调这一点。真正好的 Web UI 设计通常要求客户端上至少存在一些浏览器脚本形式的代码。例如,Web 窗体工具包中的验证程序控件确保用户使用满足其条件的数据(任何字符串、有效的电子邮件地址、5 到 15 之间的整数等)填充了表单中必需的字段,然后才允许提交窗体。如果数据未能满足验证程序的条件,该验证程序将显示一条错误信息,并中止回发。这节省了网络带宽、服务器周期,并可以防止用户产生沮丧情绪,这是因为反馈是即时的。这正是我们所希望看到的组合。



ASP.NET 框架提供了用于 Web 窗体控件的内置功能,使其能够方便地发出要放在返回到客户端的页中的脚本,并能够方便地访问它们自身或其他控件放置在该页中的脚本。我编写了一个示例程序,显示了控件可以在返回给客户端的页上放置脚本的三个位置(参见图 12、图 13图 14)。





图 12 为示例应用程序编写脚本



控件用于在页上放置脚本的所有方法都位于 Page 成员变量中,该变量表示控件所在的页。控件可以通过 Page.RegisterStartupScript 方法在页上放置一个启动脚本。在用户的浏览器中显示该页时,该脚本将自动执行。该示例程序在显示页时仅弹出一个消息框。您还可以使用 Page.RegisterClientScriptBlock 方法放置一个脚本块,该脚本块需要由页上的其他脚本代码显式调用。示例程序在您单击页上的文本时仅会弹出一个消息框。



两个方法都接受两个字符串参数:脚本块和脚本本身的名称。如果两个控件试图使用相同的名称注册一个脚本,页将忽略第二次尝试。这意味着要避免与不是由您自己编写的其他控件发生命名冲突,行业级的控件应当使用唯一的长名称,而避免使用一般性的短名称,如 MyScript。如果要在注册脚本块之前查明该脚本块是否已被注册,可以使用 Page 对象上的两个方法来实现,即 IsClientScriptBlockRegistered 和 IsStartupScriptBlockRegistered。

您所传递的脚本可以是文字的,如本示例中的脚本。但是,有时脚本可能会很长,像验证程序一样。这种情况下,您可能需要将脚本放置到单独的文件中,以便在运行时提取。本例中,可以将脚本标记编写为使用 src 属性将脚本执行引擎指向脚本文件的位置,如下所示:

<script language="javascript"
src="/aspnet_client/system_web/1_0_3328_4/WebUIValidation.js">
</script>

客户端脚本最常见的用途可能是在允许提交窗体之前对数据进行验证。为此,必须通知您的代码即将发生的回发操作,并使脚本具有在要求未能得到满足时取消回发的能力。使用 ASP.NET 框架,可以通过 Page.RegisterOnSubmitStatement 方法注册提交处理程序。提交窗体时,浏览器将逐步通过所有已注册的提交语句,以查看是否应当继续提交。您的提交语句必须包含您的验证逻辑,如果要允许提交操作继续,则返回 true,否则返回 false。示例程序将一个提交处理程序放入生成的脚本中,该脚本在单击 Submit 按钮时弹出一个提示框。如果输入“Y”,提交将继续;否则将中止操作。

并不是所有的浏览器都可以运行以您首选的语言编写的脚本,有些浏览器根本不能运行脚本。您需要一种检测这种情况的方法,这样您的控件就可以决定是要以降级方式运行,通知用户,还是根本不运行。例如,当验证程序控件无法在客户端执行其验证任务时,将在服务器端运行其逻辑。实际上,即使它们认为自己已经成功地在客户端运行,它们仍会在服务器端运行,这只是为了确保它们没有以某种方式错误地被中止或者篡改,从而将无效的数据注入服务器。



Page.Request.Browser 对象保存有关浏览器功能的信息。示例代码读取属性 Type、VBScript、JavaScript 和 EcmaScriptVersion,并将它们显示在页上供您查看。您可能还希望控件上具有一个允许页设计器关闭脚本功能(即使浏览器能够运行脚本)的属性。验证程序控件包含一个称为 EnableClientScript 的 Boolean 属性。您可能需要遵照这一设计模式,除非您的控件在没有客户端脚本功能的情况下毫无意义。


结论


Web 窗体简化了编写优秀 Web 应用程序的过程,而 .NET 框架则使您能够轻松地创建 Web 窗体控件。您所必须理解的只是控件的业务逻辑,以及如何以 HTML 呈现它;基础结构的其余部分都是由从 .NET 框架继承的类处理的。

相关文章,请参阅:



ASP .NET: Web Forms Let You Drag and Drop Your Way to Powerful Web Apps



Windows Forms: Developing Compelling User Controls that Target Forms in the .NET Framework

David S. Platt 是 Rolling Thunder Computing Inc. 的总裁和创始人。他在哈佛大学和全球各地的公司讲授 .NET 技术。他发布了一个有关 .NET 的免费电子邮件新闻稿,网址为 http://www.rollthunder.com。David 是 Introducing Microsoft .NET (Microsoft Press, 2001) 一书的作者。

 


标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@evget.com


为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP