萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> ASP.NET 程式設計

ASP.NET 防止頁面重新整理導致資料重複提交的兩種方案

使用者按 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 Token + Session 比對 (推薦,含完整基類)

核心邏輯:每次頁面渲染時,將目前 ViewState 的 MD5 雜湊存入 Session (Key 由隱藏欄位帶回);PostBack 時比對 Session 值與當前 ViewState 雜湊,若相同表示為重新整理 (非正常 PostBack),略過商業邏輯。

基類 SubmitOncePage

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();
    }
}

進階優化

  • Session Key 改用 Request.Path + User.Identity.Name 固定值,避免每次請求產生新 Key 導致 Session 膨脹。
  • 搭配 PRG (Post-Redirect-Get) 模式:寫入成功後 Response.Redirect(Request.RawUrl),瀏覽器歷史記錄變為 GET,徹底解決重整重送。
  • 現代架構建議:前端送出前 disable 按鈕、AJAX 提交、後端冪等性設計 (如唯一索引、Token 機制)。
copyright © 萬盛學電腦網 all rights reserved