深入理解 ASP.NET 动态控件

翻译|其它|编辑:郝浩|2007-09-27 11:17:59.000|阅读 843 次

概述:

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

前言

在开始写这个系列的文章之时,我想着必须深入介绍背后的原理,然后将所有需要的背景知识呈现到读者眼前,不过我现在发觉这并不是好的写作方法,要写下去对我自己来说难度也不少。最近受到  Infinities Loop  发布  TRULY Understanding Dynamic Controls (Part 4) 的刺激,我决定继续写这个系列的文章,并且领悟到了更多读者需要的是对问题的一种较为易于理解的解释,而非一种严谨的解释,因为前者更有助于读者解决当前问题并在再次遇到类似问题时自行推导解决。

 

其实我在本系列的第一篇文章就已经明确了怎样的文章才让读者容易接受,现在是我自己误入歧途了,所以必须纠正过来。第一篇文章的结尾建议读者自己阅读控件开发有关的书籍,之后就能完整理解和解决这个问题。实际上这是一个比较不合理的建议,大多数人并不可能花时间看完一本厚厚的控件开发书籍(ASP.NET 2.0的比  ASP.NET 1.x  的要厚了不少)。我需要做的不是复述书上的观点,那也不是我想要做的事情,真正需要做的事情是将书里面的观点浓缩为一篇文章,要让读者能解决问题的,推理看起来符合常识以至于容易接受,然而又比严密推理省下一大多文字。好吧,就让我们按照这种思路去看看如何解决一些常见的动态控件问题。

 

问题分类

这是受到  Infinities Loop  启发的,我们首先要将问题分类,然后逐个击破。这里的分类将最常见也最容易解决的排到上面来,然后逐步深入讨论。在开发过程中,使  ASP.NET  程序员想到要用动态控件的情景通常有如下几种:

1.        你需要呈现不确定数量的控件,但这些控件是同一类型的

2.        你需要呈现不确定类型的控件

3.        上述两个问题的综合或嵌套

4.        你需要开发自己的  Web  控件

在对问题进行分类之后,我们就容易逐个去分析解决办法了。由于分类是按照难度逐步递增的,所以这对读者来说应该是较容易理解的。

 

不确定数量的同类型控件

如果你要在页面上显示一个调查问卷,问卷的题目来自数据库,而任何问题都只有两个选项,你决定使用  RadioButton  提供选项。这时候动态创建控件的念头应该仅仅是一闪而过的,然后你就决定使用  Repeater

如果你在这时候没有想到  Repeater,或者任何的  TemplateControl,那么你就需要重新熟悉ASP.NET  的内置控件了。很多时候我们用多了  GridView,特别是一直都用  BoundField  的话,就很容易忘记世界上还有  TemplateField  这么一回事。

那么为什么  Repeater  在此会是一个好的选择呢?首先,连最近基本的  foreach  迭代循环它也帮我们做了,我们仅需要指定  DataSource,然后执行一下  DataBind(),它就帮我们动态为每一个数据项按照模板创建控件。其次,对于  PostBack  之后数据发生更新的情况它能应付自如。为了说明  PostBack  更新数据造成的影响,让我们再来看一个例子。

首先,我们直接在  Session  里存放一个  string[],内容为{"apple", "boy", "cat", "dog"},然后我们需要将它们显示出来,每一个项目显示为一个  LinkButton,点击之后就在数组中将它删除。我们都知道使用  Repeater  或者  GridView  搭配  ObjectDataSource  做这样简单的事情是绝对没问题的,但如果我们手动编写动态创建控件的过程呢?

按照大多数人所理解的  ASP.NET  逻辑,首先应该在  Load  这一阶段遍历数组,然后为每一个数据项创建一个  LinkButton,最后把这一切都附加到页面上唯一的那个  HtmlForm  上面去。删除怎么做呢?LinkButton  实现了  IButtonControl,所以可以添加  CommandArgument  属性,我们就把字符串保存进去好了。在  OnCommand  的时候就通过此属性识别当前需要删除的字符串,然后从数组中删除,并且还要在  HtmlForm  中搜索对应的  LinkButton  然后把它移除。

这时候你应该看看  OnLoad  中的代码是否记得为每一个控件的  ID  属性赋值,否则就会出问题了。页面一开始生成的结构应该是这样的:(左侧的是控件的  ID,右侧是控件显示的字符串)

ctl01 ("apple")
ctl02 ("boy")
ctl03 ("cat")
ctl04 ("dog")

 

我们点击"boy",页面进行  PostBack,然后  Load  生成同样的控件树,之后  OnDelete  删除ctl02,所以输出的控件树应该是这样的:

ctl01 ("apple")
ctl03 ("cat")
ctl04 ("dog")

 

我们这次点击"cat",页面又在  PostBack,但接着  Load  生成的控件树就不同了:

ctl01 ("apple")
ctl02 ("cat")
ctl03 ("dog")

 

必须留意到控件的 ID  属性重新编号了,然而  ASP.NET  仅仅知道我们点击了  ctl03,所以触发的  ctl03    OnCommand,根据现在的  ctl03    CommandArgument  属性,删除了"dog"字符串。这就是所谓的问题了,无指定  ID  的控件会自动按顺序分配  ID,因此  ID  具有了不确定性。

如果在  OnCommand  的时候,调用  HtmlForm    Controls.Clear(),是否就能移除所有控件并且让  ID  重头开始编号呢?实验结果表明上述删除过程中第一次  PostBack  后会生成这样的控件树:

ctl05 ("apple")
ctl06 ("cat")
ctl07 ("dog")

 

也就是说,移除确实是移除了,然而  ID  编号没有重置,而是继续编号。那么  Repeater  是怎么做到的呢?为什么直接使用  Repeater  就没有任何问题呢?这个下一篇文章再说,我们现在专心来把问题逐个击破,现在你记住这种情况选择  Repeater  或者其他更高级的数据控件就是了。

 

不确定类型的控件

在面对此类问题的时候,首先问问自己控件的数量,如果数量不多,直接通过设置控件的  Visible  属性解决问题就是了。这也就是说,把可能要显示的控件都声明为  Visible="false",然后在代码中判断当前应该将哪个显示出来。

如果控件比较多,然而还是能分组的,同一时间仅仅显示其中的一组,那么你应该考虑使用  MultiView,这样你的工作将会轻松不少。事实上,能够使用  MultiView  解决的,都应该优先考虑使用  MultiView  解决,这比起自己控制哪一个控件显示哪一个控件隐藏要方便多了。其实  MultiView  所做的,也就是帮你控制控件的显示与隐藏。

这样做的性能如何呢?我们关注两方面的问题,一方面是服务器端执行的资源消耗,另一方面是传输的带宽消耗。我们先来看看服务器端执行的资源消耗吧,我们最常见的消耗应该就是数据控件操作数据库时的消耗了。在  ASP.NET 1.x  时代,我们没有数据源控件,所以必须手动进行  DataBind(),这也就是说如果不手动执行  DataBind()的话就不会进行任何数据操作,因此只要我们记得在数据控件不显示的时候也不要让它执行  DataBind()就是了,那样就不会有性能损失。在  ASP.NET 2.0  当中,使用数据源控件的话数据控件是会自动  DataBind()的,这时候会造成控件隐藏时的资源消耗呢?事实上是不会的,数据控件即使已经定义了  DataSourceID  属性,它也仅仅在自己第一次可见时才进行自动  DataBind()。如果数据控件的状态是隐藏的(包括使用  MultiView  隐藏),它就不会自动进行  DataBind()。因此,在  ASP.NET 2.0  中使用数据源控件以及  MultiView  之后其底层过程还是和  ASP.NET 1.x  手动操作的一样,就是少写一些代码而已。

我们接着来看看带宽消耗如何,因为隐藏的控件不输出任何的  HTML,因此带宽消耗就是指ViewState  了。控件隐藏后,ViewState  是不变的,因此隐藏控件确实比完全不加载控件造成了更多的资源消耗,换取的是该控件的状态得以保存。一般来说,简单控件隐藏后多出来几十字节的  ViewState  是可以忽略不计的,整个页面中HTML缩进所需的空格也都几十上百字节了;但如果是复杂控件,拥有大量的  ViewState,这时候你真的应该考虑动态加载了。

总的来说,面对这类问题时首先判断显示隐藏控件的逻辑是否复杂,控件本身是否复杂。如果是比较简单的情况,则直接使用  MultiView  解决就是了。如果是复杂的情况,那就应该考虑自己使用控件将此逻辑封装在内,而不是直接在页面上暴露这些复杂性。关于封装控件的问题,在下一篇文章中再讨论,因此我们继续看下一类问题。

 

既不确定类型也不确定数量的控件

有时候我们面对前面两类问题都有清晰的思路,但是面对复合问题就感觉很混乱了。例如还是一个调查问卷的显示,数据来自  XML,问题类型包括单选和多选,每一道问题的选项个数也不确定,这时候怎么办呢?foreach  嵌套  foreach,外层迭代问题内层迭代选项,逐个  CheckBox/RadioButton  来生成?

这时候我们需要的是把问题分而治之逐个击破的思想。既然是上述两类问题的嵌套,我们就应该能够通过嵌套对应的解决方案来实现。对于这个调查问卷的例子,我们可以用  Repeater  来迭代问题,先把这个定下来,再考虑模板里面怎么做。模板里面需要显示的是一个不确定类型的问题,因此模板里面放一个  MutliView,把问题类型的表达式绑定到其  ActiveViewIndex  属性上,例如单选题就是0多选题就是1。然后  MultiView  里面的两个  View  各自嵌套一个  Repeater,第0  Repeater  迭代选项并显示为  RadioButton,第1  Repeater  迭代选项并显示为  CheckBox。就这样就完成了,我们没写任何一行后台代码,也没有动态创建任何控件。

然后我们来分析一下这个解决方案的性能。对比起动态创建控件,它所使用的控件确实是多了一倍,因为一道问题同时创建了两组选项,一组单选一组多选,只不过其中一组被隐藏了。然而隐藏掉的那一组唯一的服务器端资源消耗就是创建以及绑定,它们不输出任何的  HTML,因为它们的值不会被改变所以也不会输出任何的  ViewState,并且它们也不会触发任何事件,因此在对性能没有特别要求的情况下这样的性能损失还是可以接受的。至少,这比起你自己去研究  ASP.NET  页面生命周期然后自己写一大段代码来实现动态加载控件要好多了。

 

问题与实验

本系列上一篇文章的问题与实验一直没有解答,现在给出参考答案如下:

Page增加一个ShowCheckBox的属性:

1.        bool ShowCheckBox {
  get { return (ViewState["ShowCheckBox"] == null) ? false : (bool)ViewState["ShowCheckBox"]; }
  set { ViewState["ShowCheckBox"] = value; }
}
  OnLoad  的时候检测  ShowCheckBox  属性,如果为  true  则添加上该  CheckBox  控件。在  Button    OnClick  事件中,设置  ShowCheckBox    true,并添加上  CheckBox。记得这两处创建的  CheckBox  必须拥有一致的  ID  属性。

2.        这是为了让  ICallbackEventHandler  的处理模型符合页面生命周期的模型。虽然  Callback  发生的时候,页面生命周期已经与  PostBack  不同,然而  ICallbackEventHandler  还是让  Callback  模仿了  PostBack  的页面生命周期。RaiseCallbackEvent  相当于  PostBack    Raise PostBackEvent  阶段,GetCallbackResult  相当于  PostBack    PreRender  阶段。前者负责事件响应,后者负责生成返回客户端的  HTML  代码。

这次想和大家讨论的问题是,你觉得你是完美主义者吗?面对上面的调查问卷需求,你会选择我所说的  Repeater    MultiView  再套  Repeater  的做法,从而避免写任何一行后台代码,还是会选择自己封装一个控件动态创建所有控件,避免任何不必要的性能损失?

 


标签:

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

文章转载自:博客园

为你推荐

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


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP