使用者按 F5 或按「重新整理」重送 POST 表單,會造成資料庫重複寫入。本文介紹兩種經典防重機制:瀏覽器快取控制 與 ViewState Token 比對 (PRG 模式另文說明)。
protected void Page_Load(object sender, EventArgs e)
{
Response.Buffer = true;
Response.ExpiresAbsolute = DateTime.Now.AddSeconds(-1);
Response.Expires = 0;
Response.CacheControl = "no-cache";
Response.AddHeader("Pragma", "no-cache");
}
核心邏輯:每次頁面渲染時,將目前 ViewState 的 MD5 雜湊存入 Session (Key 由隱藏欄位帶回);PostBack 時比對 Session 值與當前 ViewState 雜湊,若相同表示為重新整理 (非正常 PostBack),略過商業邏輯。
using System;
using System.Web;
using System.Web.Security;
using System.Web.UI;
public class SubmitOncePage : Page
{
private const string HiddenFieldName = "__LAST_VIEWSTATE_TOKEN";
private string _sessionKey;
public SubmitOncePage()
{
_sessionKey = Guid.NewGuid().ToString();
}
/// <summary> true = 此次為重新整理 (重複提交) </summary>
public bool IsRefreshed
{
get
{
string currentToken = GetViewStateToken();
string lastToken = Session[_sessionKey] as string;
return !string.IsNullOrEmpty(currentToken) && currentToken == lastToken;
}
}
protected override void Render(HtmlTextWriter writer)
{
// 儲存目前 ViewState Token 至 Session,供下一次 PostBack 比對
Session[_sessionKey] = GetViewStateToken();
// 將 SessionKey 寫入隱藏欄位,跨請求帶回
ClientScript.RegisterHiddenField(HiddenFieldName, _sessionKey);
base.Render(writer);
}
private string GetViewStateToken()
{
string viewState = Request.Form["__VIEWSTATE"];
if (string.IsNullOrEmpty(viewState)) return null;
return FormsAuthentication.HashPasswordForStoringInConfigFile(viewState, "MD5");
}
}
public partial class WebForm1 : SubmitOncePage
{
protected void Button1_Click(object sender, EventArgs e)
{
if (!this.IsRefreshed)
{
// 只有非重新整理才執行寫入資料庫
InsertData();
}
// 重新整理時僅重繪 UI,不寫 DB
BindGrid();
}
}
Request.Path + User.Identity.Name 固定值,避免每次請求產生新 Key 導致 Session 膨脹。Response.Redirect(Request.RawUrl),瀏覽器歷史記錄變為 GET,徹底解決重整重送。