KB-Controls.AddAt可能破壞ViewState
前些時候,為了解決MatserPage下元件的ClientID會被加註Prefix的問題,我寫了一段彈性化找尋ClientID的Javascript Function取代document.getElementById(),並且為了確保WebControl在產生HTML的同時就可以插入Javascript呼叫它,我利用Page.Form.Control.AddAt(0, Literal)的技巧讓它插隊顯示在最前方。
今天同事回報,這種插隊法會讓下拉選單的選項在PostBack後掉光光,我懷疑是ViewState解析順序被破壞導致,於是寫了以下的Code驗證。以下的寫法,只要呼叫了Page.Form.Controls.AddAt(0, ...), 在按下Button1後,下拉選項就會消失。
<%@ Page Language="C#" AutoEventWireup="true" %>
<html>
<head runat="server">
<title>ViewState Is Missing</title>
</head>
<body>
<form id="form1" runat="server">
<script type="text/C#" runat="server">
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
DropDownList1.Items.Add("Item1");
DropDownList1.Items.Add("Item2");
DropDownList1.Items.Add("Item3");
}
Literal ltr = new Literal();
ltr.Text =
"<script type=\"text/javascript\">function blah() { }</"
+ "script>";
Page.Form.Controls.AddAt(0, ltr);
}
protected void Button1_Click(object sender, EventArgs e)
{
}
</script>
<div>
<asp:DropDownList ID="DropDownList1" runat="server">
</asp:DropDownList>
<asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click" />
</div>
</form>
</body>
</html>
微軟有篇文章提到這一點:
When adding a dynamic control c to some parent control p based on some condition (that is, when not loading them on each and every page visit), you need to make sure that you add c to the end of p's Controls collection.
所以囉! 用Controls.AddAt真的挺危險的在Page_Load中用Controls.AddAt真的挺危險的,但我又這麼在意要把<script>擺到最前面,怎麼辦?
改成Page.Header.Controls.Add(...)吧! 搞定收工!
Update @ 2007-01-04
網友大估找到更好的解法,將Control.AddAt()移至Page_Init()事件就可以了,這個測試結果也解釋了Control.AddAt攪亂ViewState的理由:
依Control Execution Lifecyvle中Event的順序,Load被夾在Load View State與Save State之間,因此在Load加入Contorl,會發生Save State時Control存在,下次PostBack Load State時卻Control卻還沒生出來的情況,因此造成了View State錯亂。嚴格來說,新增Control放在Init事件,會比放在Load中好。謝謝大估提供的建議!
Update 2008-01-19
強化版搜尋範圍擴及UserControl,說明在此。