網頁回應緩慢或使用者缺乏耐心時,常會連續點擊提交按鈕,導致重複送出表單、資料庫寫入重複資料、甚至觸發業務邏輯錯誤。本文整理多種 ASP.NET 防重複提交技巧,從基礎屬性注入到進階客戶端/伺服器端協作方案,滿足不同專案需求。
「點擊即停用,提交後恢復」:在按鈕的 onclick 事件中,先將按鈕設為 disabled = true,再觸發伺服器端 PostBack。若驗證失敗需取消提交,可在 JavaScript 中返回 false 並恢復按鈕狀態。
// C# 後台代碼 - Page_Load 中註冊
btnSave.Attributes.Add("onclick",
"this.disabled=true;" + ClientScript.GetPostBackEventReference(btnSave));
原理:GetPostBackEventReference 產生標準 __doPostBack 呼叫,配合 this.disabled=true 即時鎖定按鈕。
若需在停用按鈕前執行 JS 驗證(如正則檢查),必須確保驗證函式回傳 true 才繼續 PostBack:
// 後台註冊
StringBuilder sb = new StringBuilder();
sb.Append("if (!validateForm()) { return false; }"); // 驗證失敗不提交
sb.Append("this.disabled=true;");
sb.Append(ClientScript.GetPostBackEventReference(btnSave));
btnSave.Attributes.Add("onclick", sb.ToString());
// 前端驗證函式
function validateForm() {
// 例:Email 格式檢查
var email = document.getElementById('txtEmail').value;
if (!/^\S+@\S+\.\S+$/.test(email)) {
alert('請輸入有效 Email');
return false;
}
return true; // 通過驗證,繼續執行後續 disabled 與 PostBack
}
UseSubmitBehavior="false"將按鈕渲染為 <input type="button"> 而非 type="submit",完全由 OnClientClick 控制提交流程:
<asp:Button ID="btnSubmit" runat="server"
UseSubmitBehavior="false"
OnClientClick="this.value='處理中...';this.disabled=true;__doPostBack('btnSubmit','');"
Text="送出" OnClick="btnSubmit_Click" />
優點:不依賴 GetPostBackEventReference,前端完全可控,適合複雜客戶端邏輯。
使用 JavaScript 全域變數追蹤提交狀態,避免同頁多個按鈕互相干擾:
<script type="text/javascript">
var isSubmitting = false;
function safeSubmit(btn) {
if (isSubmitting) return false;
isSubmitting = true;
btn.disabled = true;
btn.value = '提交中...';
return true; // 允許 PostBack
}
// 頁面卸載時重置旗標(防使用者按瀏覽器返回鍵)
window.onbeforeunload = function() { isSubmitting = false; };
</script>
<asp:Button ID="btn1" runat="server" Text="按鈕1"
OnClientClick="return safeSubmit(this);"
UseSubmitBehavior="false" />
<asp:Button ID="btn2" runat="server" Text="按鈕2"
OnClientClick="return safeSubmit(this);"
UseSubmitBehavior="false" />
客戶端攔截可被繞過(如停用 JS),伺服器端須配合 Token 機制:
// Page_Load 產生 Token
if (!IsPostBack) {
Session["SubmitToken"] = Guid.NewGuid().ToString();
ViewState["SubmitToken"] = Session["SubmitToken"];
}
// btn_Click 驗證 Token
protected void btnSubmit_Click(object sender, EventArgs e) {
if (ViewState["SubmitToken"] == null ||
Session["SubmitToken"] == null ||
ViewState["SubmitToken"].ToString() != Session["SubmitToken"].ToString()) {
// 重複提交或 Token 失效
return;
}
// 業務邏輯處理...
// 更新 Token,供下一次提交使用
Session["SubmitToken"] = Guid.NewGuid().ToString();
ViewState["SubmitToken"] = Session["SubmitToken"];
}
| 場景 | 推薦方案 | 理由 |
|---|---|---|
| 簡單表單、單一提交按鈕 | 方案一(單行注入) | 程式碼最少、維護成本最低 |
| 需客戶端驗證、複雜邏輯 | 方案二 + 方案三 | 前端可控性最高、支援非同步驗證 |
| 多按鈕頁面、防止並發點擊 | 方案四(全域旗標) | 統一管理所有按鈕提交狀態 |
| 高安全性需求(金融、訂單) | 方案五(伺服器 Token)+ 客戶端方案 | 雙重防禦,即使繞過 JS 也能攔截 |
UpdatePanel 內,disabled 後非同步回傳可能不會自動恢復按鈕,需在 endRequest 事件中重新啟用。ValidationGroup 時,確保 OnClientClick 中先呼叫 Page_ClientValidate('GroupName')。this.value='...' 只對 input type="button" 生效,LinkButton 需用 this.innerText 或 this.textContent。