ASP.NET WebForm 在 IE 重複 PostBack 問題
3 |
有支 ASP.NET WebForm 活化石程式近期常被投訴行為異常,症狀是按送出鈕後網頁顯示找不到該筆資料,但事後查詢已處理完成。調閱 Log,發現出錯當下都有兩筆連續 POST 記錄(發出時間相同),推測是 IE 瀏覽器(該網頁為 IE Only)因不明原因一次送出兩筆 POST, 第一筆處理成功後將資料自記憶體暫存區清除,第二筆執行時便會因找不到資料而出錯。
由於是偶發,頻率不高,且表單也成功送出並不影響流程,但使用者遇到難免疑惑或抱怨,體驗不佳,還是該被解決。
爬文找到不少 IE 會重複送出一次以上的案例,較常發生於 JavaScript 呼叫表單 .submit() 方法。問題網頁是使用 input type="submit" 實體鈕送單,但 ASP.NET WebForm 有個 __doPostBack 方法,WebResource.axd 引用的 JavaScript 函式或事件會觸發 __doPostBack,嚴格來說,也有透過程式呼叫 .submit() 的可能:
function __doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm.EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
除了呼叫 .submit() 造成問題單,也不乏使用純 input type="submit" 或 button 引發重複送單的案例,例如:
- IE7 submits form twice - how to stop it?
- I am sometimes receiving duplicate submissions with the same submissions date on IE
- fix(ie): Prevent double submit of forms and duplicate ripple in IE
綜合以上線索,不管是否與 __doPostBack() 有關,都有可能是踩到 IE 的雷了。推測問題 ASP.NET WebForm 在 IE 重複送出 POST 有兩種狀況:
- 按 input type="submit" 時因不明原因產生連點兩下的效果
- 按 input type="submit" 的同時有其他邏輯觸發 __doPostBack() 形成兩個 POST
因此,只要找到一種做法可以同時防堵上述兩種狀況,即可避免問題。當務之急是先做一個可以重現問題的實驗環境,才能驗測是否能修好問題:
<%@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)
{
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();
}
}
</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 }
</style>
</head>
<body>
<form id="form1" method="post" runat="server">
<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="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>
</form>
</body>
</html>
實驗網頁運作原理為 GET 時在 input type="hidden" name="seq" 放入隨機序號,方便統計重複 POST 的次數。共有三種回應模式,Succ - 馬上回應、Delay - 延遲三秒回應(方便多點幾次送出鈕)、Fail - 拋出錯誤(用來測試如出錯能否重送)。後方的 asp:DropDownList 設定 AutoPostBack="True",會在 onchange 時觸發 __doPostBack(),故切換下拉選項會自動送出表單,用以驗證按送出鈕與 __doPostBack() 混合運作的情境。
在未加入防重送機制前,連續點擊送出鈕會產生多筆 POST:
點送出鈕再切換下拉選單觸發 __doPostBack() 也會重複 POST:
補充冷知識,重複 POST 送出表單時,前面的 POST 請求會被瀏覽器擱置或取消(但會在伺服器執行完),以最後一次傳回的結果為準:
配合 __doPostBack() 執行時會檢查 Form.onsubmit 的邏輯,我寫了一小段程式,加入 form1.onsumbit 事件(若原本已有 onsubmit,請自行串接),送單前先檢查 document.body.isPosting 屬性,若為 true 代表己送出 POST 請求在等回應,傳回 false 防止重複送單;若為 false 則設定 document.body.isPosting = true 阻止其他送出動作:
<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 />
<!-- 略 -->
</form>
加入這段簡單的程式,如下圖所示,不管是連續點擊 Submit 或是搭配切換下拉選單,就只會有一筆 POST:
在濃濃思古幽情中,我又完成一項古蹟檢修。
IE may submit the ASP.NET WebForm twice in one click, this article trys to reproduce the scenario and find the way to avoid it.
Comments
# by Slash
其實換一顆99元的滑鼠就解決了。
# by Jeffrey
to Slash, 原本有把滑鼠列為嫌犯,但有至少三名使用者報案,故先排除涉案可能。
# by Randy
近日有幸檢修公司古蹟,發現Edge與Chrome也有相同情況發生 感謝黑大無私地分享解救小弟~~