用 RadioButtonList 當 UpdatePanel Trigger 時無法正常觸發非同步更新之解法

問題:用 RadioButtonList 當 UpdatePanel Trigger 時無法正常觸發非同步更新

原始問題出處。這問題印象中我好像也碰過幾次,但一直沒有去深究原因,因為要解決並不困難 (容後說明),但今天再度看到有人提問,索性好好了解一下始末。

首先依照原發問者提供的頁面配置及程式碼重現案發現場:

--- aspx ---
<div>
    <ajaxToolkit:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
    </ajaxToolkit:ToolkitScriptManager>
    <asp:RadioButtonList ID="rblType" runat="server" OnSelectedIndexChanged="rblType_SelectedIndexChanged"
        RepeatDirection="Horizontal" AutoPostBack="true">
        <asp:ListItem Value="I" Selected="True">新增新案</asp:ListItem>
        <asp:ListItem Value="U">修改新案</asp:ListItem>
        <asp:ListItem Value="O">舊案續約</asp:ListItem>
    </asp:RadioButtonList>
    <asp:UpdatePanel ID="upl" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="False">
        <ContentTemplate>
            <asp:Panel ID="pnl" runat="server" Visible="false">
                <asp:DropDownList ID="ddlAmtYear" runat="server" Width="320" Height="23" />
            </asp:Panel>
        </ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="rblType" EventName="SelectedIndexChanged" />
        </Triggers>
    </asp:UpdatePanel>
</div>
</form>

--- cs ---
{
    if (rblType.SelectedValue == "I")
        pnl.Visible = false;
    else
        pnl.Visible = true;
}


程式很單純,判斷 RadioButtonList 的選取值以非同步更新的方式切換 Panel 隱藏 / 顯示,但執行起來果真如發問者所說,RadioButtonList.SelectedIndexChanged 初次引發時,會觸發 UpdatePanel 更新,但接下來再點回去預設選項則不會觸發非同步更新…。

身為一個網頁開發人員,遇到網頁執行結果不如預期時,把網頁開起來檢視原始碼是常用的除錯技巧,不一會兒工夫,看到一個不尋常的地方:

radiobuttonlist_html

如上圖所示,RadioButtonList 預設選取的選項,在 Render 成 HTML 後少了 "onclick=javascript:setTimeout('__doPostBack(\'rblType$0\',\'\')" 這一段,這是設定 RadioButtonList.AutoPostBack 屬性為 true 時,會自動幫每一個選項加上去的,目的是在點選任一選項之後送回 Server 端處理,要命的是預設選取的那一個選項不會加...。

知道原因之後,可以來想怎麼解決了。開頭說過我曾遇過同樣的問題,沒有深究的原因是這問題沒有耽擱到太多時間,所以一直不以為意,其實只要把 RadioButtonList 放到 UpdatePanel 裡面就不會有這樣的問題了,這是比較建議的做法。但真的不想把 RadioButtonList 丟到 UpdatePanel 裡的話也是有解,但要動點手腳,既然預設選取的選項會少指令碼,那我們就手動補上去,比較恰當的時機點是在 RadioButtonList.PreRender 事件處理

{
    RadioButtonList radioButtonList = ((RadioButtonList)sender);

    foreach (ListItem li in radioButtonList.Items)
    {
        // 預設選取的選項加上 onclick 事件指令碼
        if (li.Selected)
        {
            li.Attributes.Add("onclick",
                String.Format("javascript:setTimeout('__doPostBack(\\'{0}${1}\\',\\'\\')', 0)",
                radioButtonList.UniqueID,
                radioButtonList.Items.IndexOf(li)));
        }
    }
}


加上必要的指令碼後,RadioButtonList 不需在 UpdatePanel 裡也能正常觸發非同步更新。如果覺得這樣不夠簡潔,應該是可以進一步打包成 Custom Control 來使用,這樣就不需每次補程式碼了,這部分就留待有興趣的人自行開發囉!


※備註:這問題後來我在網路上搜尋,找到許多相關討論 (早知道就不必花時間追查了…),例如 MSDN Forums 的這一篇這一篇,發現其實存在已久,另外在 ASP.NET Forums討論指出,似乎在 ASP.NET AJAX 早期的版本 (Atlas?CTP?不是很確定…),設定 RadioButtonList、CheckBoxList 為 UpdatePanel 的 Trigger 時,仍會造成整頁刷新 (Full PostBack, NOT AsyncPostBack)。不太清楚為何時至今日仍未完全修正 (我指的是還有本篇的問題),以微軟之能,這應該老早就要被修正了吧?難道是我錯過了甚麼嗎?(資訊焦慮症發作中…@.@a)