关于WebForm,十分惭愧(上)
想要写这篇文章的时候,我感到非常非常的惭愧,一则是时间与文章数量之间巨大差异,当初建博客的初衷全被狗吃了。二则是这篇文章的主题是关于控件生命周期的。我工作已经超过5年了,现如今与每天吃饭的家伙才有点开窍的感觉,不能不说是不学无术。但我又是个烂记性,所以还是把记录的优先级调到最高好了,忽略丢人这件事。能够自我安慰的就是无论是同学,同事,还是在我搜索资料过程中发现的绝大多数文章,其实我敢肯定他们也比我强不了多少。你能搜到大量的关于WebForm控件生命周期的文章,但...那些文字总给人道貌岸然的感觉。他们看了些外国文章,贴了些外国图片,他们解释了一部分现象的原因,但真的了解了吗?ASP.NET机制是个辆复杂的汽车,不去修理可能很难说(应该“是绝对不能说”)真的了解。
虽然如此自我安慰,但我还是有一种做了“复古事情”的落寞感。
免责声明:本文目的仅仅用于个人探索的记录,主要形式是试验,充斥着大量的无根据的猜想,以待日后完善。对于误入此房间的同学请擦亮眼睛,不要当做“真相”来读,请自行思考研究,本人概不对误人子弟负责。但如果发现问题也请和善的指出(否则我写好后保存在本地好了)。
正文
目前我正在做一个网站,要求有些苛刻。我需要根据一个确定数据结构的对象实体(下文称为Format),来决定我在页面上输出哪些控件,然后待用户填写完成后,我再根据其格式将信息数据封装好记录下来。之所以说苛刻是因为这个格式自由度很高,难以抽象,必然导致代码繁琐。但这并不是本文主题。由于时间非常仓促,我暂时使用的解决方案是用一个用户控件将所有将要输出的控件包含进去,用Format判断哪个Visible是true。Format的种类多,而且重复可能性高,我可能要为每个种类放2,3个同样的控件,但却很少用得上。能预见到这效率很低很低,实际情况也是如此。(主题无关:但我不确定拖累Performance的是不是那个超大的dropdownlist,甚至不知道它在updatepanel里post多少数据,这是下一步的研究重点,select post什么,autoPostback的select post什么,在updatepanel里的select post什么,刷新什么)
如果用动态添加控件,问题看似很简单,保存Format,生成控件,重新加载控件。但我遇到了个问题。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
protected bool IsDynamicLoadControl
{
get
{
object dynamic = ViewState["IsDynamicLoadControl"];
return dynamic == null ? false : true;
}
set
{
ViewState["IsDynamicLoadControl"] = value;
}
}
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
if (IsDynamicLoadControl)
{
Reload();
}
}
protected void Page_Load(object sender, EventArgs e)
{
}
public void Refresh()
{
HtmlGenericControl div = new HtmlGenericControl("div");
div.ID = "divTest";
TextBox text = new TextBox();
text.ID = "textDollers";
RadioButtonList radio = new RadioButtonList();
radio.ID = "radioTest";
radio.DataSource = new List<string>() { "aaa", "bbb" };
radio.DataBind();
HtmlGenericControl div2 = new HtmlGenericControl("div");
div2.ID = "divTest2";
div.Controls.Add(radio);
div2.Controls.Add(text);
divRun.Controls.Add(div);
divRun.Controls.Add(div2);
IsDynamicLoadControl = true;
}
protected void Reload()
{
HtmlGenericControl div = new HtmlGenericControl("div");
div.ID = "divTest";
TextBox text = new TextBox();
text.ID = "textDollers";
RadioButtonList radio = new RadioButtonList();
radio.ID = "radioTest";
radio.DataSource = new List<string>() { "aaa", "bbb" };
radio.DataBind();
HtmlGenericControl div2 = new HtmlGenericControl("div");
div2.ID = "divTest2";
div.Controls.Add(radio);
div2.Controls.Add(text);
divRun.Controls.Add(div);
divRun.Controls.Add(div2);
}
public string GetString()
{
//HtmlGenericControl div = divRun.FindControl("divTest") as HtmlGenericControl;
//RadioButtonList radio = div.FindControl("radioTest") as RadioButtonList;
//return radio.SelectedValue;
HtmlGenericControl div2 = divRun.FindControl("divTest2") as HtmlGenericControl;
TextBox t = div2.FindControl("textDollers") as TextBox;
return t.Text;
}
OK, 这是一般你能找到的文章推荐的做法,做一个用户控件,动态添加TextBox和RadioButtonList, Refresh方法用于第一次生成这动态的UI,Reload和它几乎已一摸一样,在PostBack之后,你需要重建这些动态控件(因为他们不在ASPX上,没经历Init之前那些步骤),让它们的ID与之前的对应,数据就能自动对应上。
在你的页面上引用这个控件,摆两个按钮,第一个触发Refresh,第二个调用GetString输出结果(或者Response.write,或者Trace)。记得先触发Refresh,再触发GetString。
Reload看起来只是Refresh的翻版,你凭什么说Reload生成的控件的数据对应上之前Refresh中生成的了呢?先Reresh,生成UI,往TextBox里写个值,GetString,OK,你会发现对应成功,得到的就是你填的那个值。
但每次Reload时都要重新绑定Radios的数据源,难道也算是“完美的还原”?试着去掉Reload中绑定Radios数据源的部分,将Getstring代码改为注释的部分。结果是在生成UI后再次POSTBACK(即点击触发GetString的那个按钮)后,即没取到select的值,连Radios也不见了。
查阅资料,发现原因。http://www.cnblogs.com/end/archive/2007/03/08/668054.html
即,动态生成的控件,加入到控件树中时,要进行追赶,在调用TrackViewState之前对这个控件的一些属性操作,不能触发Dirty为true,系统不认为此属性修改了,在SaveViewState时也就不需要保存(出于减少页面大小的目的,一个属性从未改变,再次生成的时候只要带着Init步骤之前ASPX上的初始值就好)。关于这个机制请另行查阅ViewState相关文章,反正我是记得。可以理解为,在将你的控件加入到树之前,他还不算这个大家庭的一员,只有登记了户口,我才会为他记录相关的信息。
我们调整Refresh中顺序,先生成控件,赋予ID,加入到树中,再绑定数据源。删除掉Reload中绑定Radio的代码,测试通过,PostBack后,Radio的数据还在,说明成功还原了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public void Refresh()
{
HtmlGenericControl div2 = new HtmlGenericControl("div");
div2.ID = "divTest2";
divRun.Controls.Add(div2);
TextBox text = new TextBox();
text.ID = "textDollers";
div2.Controls.Add(text);
HtmlGenericControl div = new HtmlGenericControl("div");
div.ID = "divTest";
divRun.Controls.Add(div);
RadioButtonList radio = new RadioButtonList();
radio.ID = "radioTest";
div.Controls.Add(radio);
radio.DataSource = new List<string>() { "aaa", "bbb" };
radio.DataBind();
IsDynamicLoadControl = true;
}
上面的代码,只要保证radio在加入到“大家庭”后再绑定就可以,无所谓是先把div加到divRun中,还是先把radio加到div中,原因显而易见。(主题无关:有趣的是,在Reload中,两个div的加入顺序要保持和Refresh一样,这是ViewState生成和还原的规则,可以用ViewStateDecoder解码看看其结构也是树形的,猜想肯定顺序是其进行 数据-控件对照的依据,这样会比键值方式节省大量空间,下一步需要研究研究这个,但对我火烧眉毛的开发暂时没用。还有,跟踪UserControl的LoadViewState方法,只发现了IsDynamicLoadControl的数据提供给你,原因也是显而易见的---自己拿自己的东西。可以想象系统在LoadViewState时遍历控件树,然后顺次把ViewState转换的那个啥对象分发出去)
我们可以再测试下,TextBox,在Refresh中,TextBox加入到div之前就对Text属性赋值,执行测试步骤,发现你给的值被成功取出来了...
为什么?因为Text值是输出到input的value上的,最终是要Post的,而LoadPostData又在LoadViewState之后,虽然这个Text并不记录在你ViewState中,但会在LoadPostData步骤给你的控件赋值。(猜想:我甚至不知道Text这个属性,即input[type='text']中的value,和其他不会Post的属性 是否一样记录ViewState,你会为一个页面上一直存在的,每次都会回传值留一个空间保存吗?看起来是否定的,但别忘了,最终页面并不等于翻译后的ASPX,有的控件Visible=false,页面上就不存在了,但你依旧能操作他。设想ASPX上有TextBox, Text=“100”,某次回发后改变为了1,然后控件被隐藏了,没有post数据,没有viewstate,那你后台还会是100。由此可见,还是需要保存的)
还有点东西,下篇再谈,字太多了实在卡。