Response.Buffer = true;
Response.ExpiresAbsolute = DateTime.Now.AddSeconds(-1);
Response.Expires = 0;
Response.CacheControl = "no-cache";
原理:讓瀏覽器認為頁面已過期,使用者按「上一頁」或重整時會提示「網頁已過期」。缺點:現代瀏覽器行為不一,無法徹底阻止重複 POST,僅作輔助。
302 Redirect 導向同一頁面(或結果頁)的 GET 請求。// 處理完新增邏輯後
Response.Redirect(Request.RawUrl); // 或 Response.Redirect("Success.aspx");
適用於**必須保留頁面狀態(如驗證失誤需回填表單)無法使用 PRG** 的情境。
SessionKey (Guid),寫入隱藏欄位 __LastVIEWSTATE_SessionKey。ViewState 的 MD5 雜湊,存入 Session[SessionKey]。Session[SessionKey] == 目前 ViewState MD5 → 判定為**重新整理**(ViewState 未變)。public class SubmitOncePage : System.Web.UI.Page
{
private string _sessionKey;
private const string HiddenFieldName = "__LastVIEWSTATE_SessionKey";
private string _lastViewStateHash;
public SubmitOncePage()
{
_sessionKey = Guid.NewGuid().ToString();
}
public bool IsRefreshed
{
get
{
string currentHash = GetViewStateHash();
_lastViewStateHash = currentHash;
string storedHash = Session[GetSessionKey()] as string;
return !string.IsNullOrEmpty(currentHash) &&
!string.IsNullOrEmpty(storedHash) &&
currentHash == storedHash;
}
}
protected override void Render(HtmlTextWriter writer)
{
// 頁面輸出前,將「上一次」的 ViewState Hash 存入 Session
Session[GetSessionKey()] = _lastViewStateHash;
// 將 SessionKey 寫入隱藏欄位,下次 PostBack 時帶回
ClientScript.RegisterHiddenField(HiddenFieldName, GetSessionKey());
base.Render(writer);
}
private string GetSessionKey()
{
return Request.Form[HiddenFieldName] ?? _sessionKey;
}
private string GetViewStateHash()
{
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)
{
// 真正的業務邏輯:寫入 DB、發送 Email 等
InsertData();
}
// 重新整理時這裡不會執行,但頁面仍正常渲染
}
}
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| Cache-Control | 零程式碼修改 | 不可靠、瀏覽器相容性差 | 輔助用 |
| PRG (Redirect) | 標準、無 Session 消耗、支援瀏覽器歷史 | 無法保留表單輸入值(驗證失敗時) | 大多數新增/編輯頁面 |
| ViewState Token | 保留頁面狀態、無需 Redirect | 消耗 Session、增加 ViewState 大小、需繼承基底類別 | 複雜表單、驗證失敗需回填 |
AntiforgeryToken + PRG 組合,或 Idempotency Key 模式。