10

【茶包射手日記】防止 WebForm 重複 PostBack 與 UpdatePanel

 3 years ago
source link: https://blog.darkthread.net/blog/updatepanel-submit/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
【茶包射手日記】防止 WebForm 重複 PostBack 與 UpdatePanel-黑暗執行緒

今年初為解決 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 程式打架囉。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK