今年初為解決 ASP.NET WebForm 在 IE 會重複 PostBack 問題,我想到在 Form onsubmit 事件加入控制旗標的解法,實測能有效預防內含 AutoPostBack 欄位 WebForm 的重複 PostBack 行為。近日接到同事報案,說他嘗試套用後,會讓某些表單無法送單。

比對分析後,發現問題與 UpdatePanel 有關。(年輕朋友們應該不知道 UpdatePanel 是什麼鬼,但當年它可是讓不懂 JavaScript 的 .NET 工程師也能寫出 AJAX 動態網頁的黑魔法呢?只是後來 JavaScript 成了人人都要會的基本技能,靠著傳送龐大資料實現魔法的它就開始被嫌棄還慢慢臭掉了...)

原本的寫法是在表單送出時設定 document.body.isPosting = true,之後若重複送單也觸發 onsubmit 事件就會被擋下。而完成 PostBack 後,頁面刷新,body.isPosting 會被重設成 false,允許送單。

var body = document.body;
body.isPosting = false;
document.getElementById("form1").onsubmit = function() {
    if (body.isPosting) return false;
    body.isPosting = true;
    return true;
};

問題來了,UpdatePanel 更新時也會觸發 onsubmit,但只更新 UpdatePanel 範圍非整頁刷新,body.isPosting 維持 true,阻擋了所有後續動作。

先修改上次文章的範例,加入 UpdatePanel 重現問題:

<%@Page Language="C#"%>
<script runat="server">
    //TODO: 示範用,忽略記憶體清除作業
    static Dictionary<string, string> data = new Dictionary<string, string>();
    void Page_Load(object sender, EventArgs e)
    {
        if (Page.IsPostBack && Request["__EVENTTARGET"] != "ddlUpdPanelCode") 
        {
            var seq = Request["seq"];
            var mode = Request["mode"];
            data[seq] = (data.ContainsKey(seq) ? data[seq] : string.Empty) + DateTime.Now.ToString("ssfff") + "\n";
            switch (mode) 
            {
                case "Fail":
                    throw new ApplicationException("啊,挫賽! 玩壞了。");
                case "Delay":
                    System.Threading.Thread.Sleep(3000);
                    break;
            }
            Response.ContentType = "text/plain";
            Response.Write("*** Result ***\n" + data[seq]);
            Response.End();
        }
    }
    protected void ddlUpdPanelCode_SelectedIndexChanged(object sender, EventArgs e)
    {
        lblUpdPanelMsg.Text = ddlUpdPanelCode.SelectedValue;
    }
</script>
<!DOCTYPE html>

<html>
<head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=7" />
    <style>
        input[readonly] { width: 2.5em; color: #666; background-color: #eee }
        .upd-panel { background-color: #eee; padding: 3px; margin: 6px; width: 185px; }
        .upd-panel > * { font-size: 10pt; display: inline; }
    </style>
</head>
<body>
    <form id="form1" method="post" runat="server">
        <script>
            var body = document.body;
            body.isPosting = false;
            document.getElementById("form1").onsubmit = function() {
                if (body.isPosting) return false;
                body.isPosting = true;
                return true;
            };
        </script>
        <asp:ScriptManager runat="server" />
        <input type="text" name="seq" value="<%=Guid.NewGuid().ToString().Substring(0, 4)%>" readonly />
        <select name="mode">
            <option>Succ</option>
            <option>Delay</option>
            <option>Fail</option>
        </select>
        <input type="submit" name="submit_action" value="Submit" />
        <asp:DropDownList runat="server" AutoPostBack="True">
            <asp:ListItem>1</asp:ListItem>
            <asp:ListItem>2</asp:ListItem>                
            <asp:ListItem>3</asp:ListItem>
        </asp:DropDownList>
        <div class="upd-panel">
        <asp:DropDownList ID="ddlUpdPanelCode" runat="server" 
            AutoPostBack="True"
            OnSelectedIndexChanged="ddlUpdPanelCode_SelectedIndexChanged">
            <asp:ListItem>Please select...</asp:ListItem>
            <asp:ListItem Value="ABC">ABC</asp:ListItem>
            <asp:ListItem Value="XYZ">XYZ</asp:ListItem>
        </asp:DropDownList>
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>
            <asp:Label ID="lblUpdPanelMsg" runat="server" Text="Code"></asp:Label>
        </ContentTemplate>
        <Triggers>    
            <asp:AsyncPostBackTrigger ControlID="ddlUpdPanelCode" EventName="SelectedIndexChanged" />
        </Triggers>
        </asp:UpdatePanel>           
        </div>
    </form>
</body>
</html>

如以下實測,沒觸發 UpdatePanel 更新前運作如常,重複操作右側 AutoPostBack 下拉及 Submit 鈕也會導致重複 PostBack。但只要動一次下方的 Code 下拉選單觸發 UpdatePanel,之後 Code 下拉選單、Submit 鈕,跟右側的 123 下拉選單全都失去作用。

清朝火砲發射不順,值得花時間檢修嗎?深深吸了一口氣,我勇敢打開 F12 潛進 ASP.NET AJAX JavaScript 原始碼,找到一個神祕狀態變數可用來識別 onsubmit 事件是否來自 UpdatePanel Trigger - Sys.WebForms.PageRequestManager.getInstance()._postBackSettings.async,當它為 true 表示 PostBack 動作來自 UpdatePanel,不要把 body.isPosting 設成 true 就能避開問題。

var body = document.body;
body.isPosting = false;
document.getElementById("form1").onsubmit = function() {
    //Ignore UpdatePanel postback
    if (Sys.WebForms.PageRequestManager.getInstance()._postBackSettings.async)
        return true;
    if (body.isPosting) return false;
    body.isPosting = true;
    return true;
};

修改後,UpdatePanel 就不會跟防重複 PostBack 程式打架囉。

Try to find a way to detect form submit by UpdatePanel.


Comments

Be the first to post a comment

Post a comment