前些時候,為了解決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,說明在此


Comments

# by 大估

Dear 黑暗大大: 昨天在套用你介紹的「解決MatserPage下元件的ClientID」方法時,就發現套用後,當網頁有postback時,menu或是tree的資料都會全部清空,今天上來就發現您的修正檔,真是感謝啦~ 不過,大估提出另一個解法,也就是將您原本的程式(Page.Form.Controls.AddAt),放在MasterPage的page_Init中,就ok了,menu及tree在postback後,也不會消失了,蒙古的馬照跑,舞照跳…

# by 大估

Sorry補上程式 Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init MasterPageHelper.RegisterMPGet(Me) End Sub 以上

# by Jeffrey

To 大估, 這解法很優! 也算是結實地跟大家上了一堂Control Execution Lifecycle的Event順序課程,Initialize在Load view state與Save state之外,而Load則被夾在Load state與Save state之間,在Load時才加物件,Save時存在的東西,Load時還沒生出來,就錯亂了。Good Job!! http://msdn2.microsoft.com/en-us/library/aa719775(VS.71).aspx

# by Jet Tseng

這問題 搞了我一早上 最後測試出AddAt 造成的 Google 看到你的文章 你的經驗豐富 真是令我崇拜

# by Hung Yang

最近在處裡<asp:DataList>存放動態生成欄位的<asp:GridView>時也遇到viewsdate損壞的問題 動態的gridview如果是單獨存在頁面上的靜態元件,可以在Page_Init進行處裡。 但是在 Page_ load之後產生在DataList裡的GridView,就不知道要如何能在Page_Init進行處裡了。 想請問黑大有處裡過這類比較麻煩的動態控制項操作嗎?能否指點小弟一些方向,謝謝。

# by Jeffrey

to Hung Yang, 有無可能將 Page_Load 中的動作移到 Page_Init 處理?

Post a comment