其实这个问题就像搂主说的,是在后来的回发中没有再一次创建同样的控件造成的。一般地,用户控件特定的内容可以使用ControlState在回发之间保存。ViewState用来保存容器的状态是比较合理的。但使用ViewState存在一个动态加载控件时机的问题。
我在这里简单按发生先后次序列出几个主要的页面事件:
PreInit
Init
InitComplete
PreLoad
Load
(controls events, such as someButton.Click event)
LoadComplete
比较正宗的加载动态控件的事件似乎是在Init时,因为这样在后续的处理中可以借助asp.net页面处理的机制自动完成动态加载的控件的值的回填。就是说,asp.net会处理ViewState和ControlState,一遍能够把动态加载的控件的值正确地设置到控件的属性中。这样在Load事件里就能够读到控件的值了。这在某些情形下还是有用处的,因为可能会依赖某个控件的取值而使用不同的数据绑定逻辑来初始化页面。比如说在业务系统根据中不同的销售方式可能会造成可选的促销品列表发生变化。
但要在Load(确切地说是PreLoad)之前动态加载控件还存在一些困难。经常地,我们会把需要动态加载的哪些控件的一些标记性的信息放在ViewState里。但这里存在一个asp.net在何时加载ViewState的问题。事实上,asp.net页面会两次尝试加载ViewState(至少目前根据实验得到的证据表明是这样的)。第一次是发生在InitComplete以后,第二次是在Load事件处理完成以后。至于为什么这样做我们只能猜测。一个事实是这样做我们在控件的事件处理程序中(比如someButton.Click)是可以通过控件的属性取得控件的取值的。然而依赖asp.net页面自己的处理过程,在Init阶段是无法得到ViewState的值的。于是我们就无法知道应该动态加载哪些控件。但如果把动态加载控件放在Load中,我们就无法在Load中通过控件的属性获取控件的取值,从而无法完成复杂的数据绑定初始化的逻辑。
我再把这个问题直观地表达在下面:
PreInit
Init (此时asp.net页面处理过程尚未加载ViewState,因此无法获得我们在需要动态加载哪些控件的信息)
InitComplete
<---- asp.net页面处理过程在这里尝试加载ViewState
PreLoad
Load
<---- asp.net页面处理过程在这里再次尝试加载ViewState
控件的事件处理程序 (在这里可以获取控件的值)
LoadComplete
...
这样一来问题变成了我们如果要使用ViewState来保存动态加载控件的信息,则只能在Load阶段加载控件。而这样又无法完成复杂的数据绑定逻辑。所以为了解决问题,很多人选择使用Session等其它机制来保存要动态加载哪些控件的信息。这种解决方案不是不可以,但有一些弊端。其一是增加了服务器端的开销;其二是Session有过期的问题;其三是Session不是局限于某个页面的,有可能存在Session中Key的冲突从而存在造成某些问题的风险。
而使用其它的机制比如说用某个hidden来存储该加载哪些控件的信息,而在Init阶段使用Request.Form来获得hidden的内容。这种方式也总让人感觉不大美妙。
实际上要解决这个问题并没有什么太美妙的方法。asp.net 2.0提供了一个扩展机制 - PageStatePersister。PageStatePersister只是一个抽象基类,如果要从页面的__VIEWSTATE中获取,则可以使用HiddenFieldPageStatePersister类。使用这个机制可以在你想要的时候手动为页面加载ViewState。这样就可以在要动态加载控件前手动加载ViewState了。关于这个扩展机制,我在这里提供一些链接供大家参考。
http://aspnetresources.com/blog/page_state_persisters_overview.aspx
http://aspnet.4guysfromrolla.com/articles/011707-1.aspx