首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

ASP.NET ViewState 過大解決方案:自訂 PageStatePersister 將 ViewState 移至伺服器端檔案儲存

     編輯:ASP.NET 基礎

問題背景:ViewState 膨脹的副作用

ASP.NET Web Forms 預設將 ViewState 序列化為 Base64 字串,置於隱藏欄位 __VIEWSTATE。當頁面包含大量資料繫結控制項(GridView、Repeater)或啟用大量控制項 ViewState 時,隱藏欄位動輒數十 KB 甚至百 KB,導致:

  • 頁面下載體積暴增,首屏渲染延遲。
  • 表單提交 (POST) 回傳大量無用資料,頻寬浪費。
  • 部分 Proxy/防火牆攔截過大的 POST Body。

核心解法:重寫 PageStatePersister,落地至伺服器檔案系統

繼承 Page 並覆寫 LoadPageStateFromPersistenceMedium / SavePageStateToPersistenceMedium,配合 PageStatePersister 抽象類,將 ViewState/ControlState 序列化後存為伺服器端實體檔案,頁面僅保留檔案識別碼(GUID/Ticks+Hash)。

完整實作:XVPage 基底類別

public class XVPage : Page {
    // === 目錄管理 ===
    static private DirectoryInfo _viewStateDir;
    private DirectoryInfo ViewStateDir {
        get {
            if (_viewStateDir == null) {
                var baseDir = new DirectoryInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data"));
                if (!baseDir.Exists) baseDir.Create();
                _viewStateDir = new DirectoryInfo(Path.Combine(baseDir.FullName, "ViewState"));
                if (!_viewStateDir.Exists) _viewStateDir.Create();
            }
            return _viewStateDir;
        }
    }

    // === 讀取 ===
    protected override object LoadPageStateFromPersistenceMedium() {
        var ps = this.PageStatePersister;
        ps.Load();
        if (ps.ControlState != null) 
            ps.ControlState = DeserializeFromStore((string)ps.ControlState);
        if (ps.ViewState != null) 
            ps.ViewState = DeserializeFromStore((string)ps.ViewState);
        return new Pair(ps.ControlState, ps.ViewState);
    }

    // === 寫入 ===
    protected override void SavePageStateToPersistenceMedium(object state) {
        var ps = this.PageStatePersister;
        if (state is Pair pair) {
            ps.ControlState = pair.First;
            ps.ViewState = pair.Second;
        } else {
            ps.ViewState = state;
        }
        if (ps.ControlState != null) 
            ps.ControlState = SerializeToStore(ps.ControlState);
        if (ps.ViewState != null) 
            ps.ViewState = SerializeToStore(ps.ViewState);
        ps.Save();
    }

    // === 序列化 → 檔案 + Cache 雙層加速 ===
    private string SerializeToStore(object obj) {
        string serialized = new ObjectStateFormatter().Serialize(obj);
        string fileId = (DateTime.Now.Ticks + (long)serialized.GetHashCode()).ToString(); // 離散 ID
        string filePath = Path.Combine(ViewStateDir.FullName, fileId);
        File.WriteAllText(filePath, serialized);
        Cache.Insert(fileId, serialized); // 熱資料進 Cache,下次讀取命中記憶體
        return fileId; // 僅回傳 ID 給 __VIEWSTATE 隱藏欄位
    }

    private object DeserializeFromStore(string fileId) {
        // 先嘗試 Cache
        string serialized = Cache[fileId] as string;
        if (serialized == null) {
            string filePath = Path.Combine(ViewStateDir.FullName, fileId);
            if (File.Exists(filePath)) serialized = File.ReadAllText(filePath);
        } else {
            Cache.Remove(fileId); // 讀取後移除 Cache,避免殘留
        }
        return new ObjectStateFormatter().Deserialize(serialized);
    }

    // === 定期清理過期檔案(20 分鐘未存取)===
    protected override void OnUnload(EventArgs e) {
        base.OnUnload(e);
        var cutoff = DateTime.Now.AddMinutes(-20);
        foreach (var file in ViewStateDir.GetFiles()) {
            try {
                if (file.LastAccessTime < cutoff) file.Delete();
            } catch { /* 忽略佔用/權限錯誤 */ }
        }
    }
}

使用方式:所有頁面繼承 XVPage

// 原本:public partial class MyPage : Page
public partial class MyPage : XVPage { /* ... */ }

關鍵設計解析

設計點說明
ObjectStateFormatterASP.NET 內建序列化器,支援 ViewState 所需的型別(Pair、Triplet、IndexedString 等),比 BinaryFormatter 安全、輕量。
ID 生成策略Ticks + HashCode 極低衝突機率,無需 GUID 產生開銷。
Cache + 檔案雙層熱資料走記憶體(Cache),冷資料落盤;讀取後移除 Cache,配合 OnUnload 定期清理,防止檔案無限堆疊。
ControlState 同步處理ControlState(如 GridView 排序/分頁資訊)同樣走此管道,確保控制項核心狀態不丟失。
App_Data/ViewState 目錄受 ASP.NET 保護(預設拒絕直接 HTTP 存取),安全性優於任意目錄。

替代方案對比

方案優點缺點適用場景
伺服器端檔案 (本文)零架構變更、相容既有專案、無需額外基礎設施Web Farm 需共享儲存(NFS/DFS)、IO 壓力大時需 SSD中小型單機/少量負載平衡節點
SessionPageStatePersister內建、程式碼極少Session 暴增、InProc 模式回收即丟失、StateServer/SQL Server 序列化開銷大極簡專案、單伺服器、ViewState 极小
自訂 SQL/Redis PageStatePersisterWeb Farm 原生支援、集中管理、可查詢審計需額外基礎設施、延遲略高、實作複雜度高大型分散式系統、高可用性要求
關閉 ViewState + 控制項層級啟用根治體積、效能最佳開發成本高、易遺漏導致 Bug、需深度理解控制項生命週期新專案、團隊有經驗、極致效能要求

遷移提示:Web Farm 部署時的調整

  1. 將 ViewStateDir 指向共享網路磁碟(檔案鎖定需測試)或改寫為 Redis/SQL 實作。
  2. 確保所有節點 machineKey 一致(ObjectStateFormatter 依賴 machineKey 簽章/加密)。
  3. 清理策略改為集中式 Job(SQL Agent / Hangfire),避免多節點競爭刪除。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved