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

ASP.NET 伺服器控件開發基礎 (11):自訂視圖狀態管理完整指南

     編輯:關於ASP.NET

前言:視圖狀態是 WebForms 狀態管理的核心

HTTP 無狀態本質導致每次請求控件屬性重置。ASP.NET 視圖狀態 透過隱藏欄位 __VIEWSTATE 在伺服器與客戶端間往返攜帶控件狀態。掌握自訂視圖狀態管理,是開發高效能、可維護伺服器控件的關鍵技能。

視圖狀態運作機制


// 頁面渲染時自動產生


// 核心類別
public class StateBag : IStateManager
{
    private IDictionary _values = new HybridDictionary();
    private bool _tracking;
    
    public object this[string key] 
    { 
        get => _values[key]; 
        set 
        { 
            _values[key] = value; 
            if (_tracking && value != null)
                _values[key] = value; // 標記已修改
        } 
    }
    
    public void TrackViewState() => _tracking = true;
    public bool IsItemDirty(string key) => ...;
    public object SaveViewState() => ...; // 序列化為物件圖
    public void LoadViewState(object savedState) => ...; // 反序列化
}

控件層級視圖狀態覆寫


public class CustomControl : WebControl
{
    // 1. 簡單屬性使用 ViewState
    public int Age
    {
        get => ViewState["Age"] != null ? (int)ViewState["Age"] : 0;
        set => ViewState["Age"] = value;
    }
    
    public string Name
    {
        get => ViewState["Name"] as string ?? string.Empty;
        set => ViewState["Name"] = value;
    }
    
    // 2. 列舉型別
    public Metier CustomMetier
    {
        get => ViewState["CustomMetier"] != null ? (Metier)ViewState["CustomMetier"] : Metier.程序員;
        set => ViewState["CustomMetier"] = value;
    }
    
    // 3. 複雜屬性 (需自訂視圖狀態)
    public Address CustomAddress
    {
        get
        {
            if (_address == null)
            {
                _address = new Address();
                if (IsTrackingViewState)
                    ((IStateManager)_address).TrackViewState();
            }
            return _address;
        }
    }
    private Address _address;
    
    // 4. 覆寫視圖狀態方法 (包含複雜屬性)
    protected override object SaveViewState()
    {
        object baseState = base.SaveViewState();
        object addressState = _address != null ? ((IStateManager)_address).SaveViewState() : null;
        
        return new Pair(baseState, addressState);
    }
    
    protected override void LoadViewState(object savedState)
    {
        if (savedState == null) return;
        
        Pair state = (Pair)savedState;
        base.LoadViewState(state.First);
        
        if (state.Second != null)
        {
            if (_address == null) _address = new Address();
            ((IStateManager)_address).LoadViewState(state.Second);
        }
    }
}

複雜物件實作 IStateManager

Address 類別完整實作


[Serializable]
public class Address : IStateManager
{
    private StateBag _viewState = new StateBag();
    private bool _tracking;
    
    // 屬性
    public string City
    {
        get => _viewState["City"] as string ?? string.Empty;
        set => _viewState["City"] = value;
    }
    
    public string State
    {
        get => _viewState["State"] as string ?? string.Empty;
        set => _viewState["State"] = value;
    }
    
    public string Street
    {
        get => _viewState["Street"] as string ?? string.Empty;
        set => _viewState["Street"] = value;
    }
    
    public string Zip
    {
        get => _viewState["Zip"] as string ?? string.Empty;
        set => _viewState["Zip"] = value;
    }
    
    // IStateManager 實作
    public bool IsTrackingViewState => _tracking;
    
    public void LoadViewState(object savedState)
    {
        if (savedState != null)
            _viewState.LoadViewState(savedState);
    }
    
    public object SaveViewState()
    {
        return _viewState.SaveViewState();
    }
    
    public void TrackViewState()
    {
        _tracking = true;
        _viewState.TrackViewState();
    }
}

視圖狀態最佳實踐與效能優化

1. 關閉不需要的視圖狀態


// 控件層級


// 頁面層級
<%@ Page EnableViewState="false" %>

// 程式碼層級
protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
    this.EnableViewState = false; // 動態關閉
}

2. 壓縮視圖狀態


public class CompressedPage : Page
{
    protected override void SavePageStateToPersistenceMedium(object viewState)
    {
        // 使用 GZip 壓縮
        byte[] bytes = Serialize(viewState);
        byte[] compressed = GZipCompress(bytes);
        string base64 = Convert.ToBase64String(compressed);
        ClientScript.RegisterHiddenField("__VIEWSTATE_COMPRESSED", base64);
    }
    
    protected override object LoadPageStateFromPersistenceMedium()
    {
        string base64 = Request.Form["__VIEWSTATE_COMPRESSED"];
        if (!string.IsNullOrEmpty(base64))
        {
            byte[] compressed = Convert.FromBase64String(base64);
            byte[] bytes = GZipDecompress(compressed);
            return Deserialize(bytes);
        }
        return base.LoadPageStateFromPersistenceMedium();
    }
}

3. 移至伺服器端儲存 (Session/ViewStateProvider)


public class CustomViewStatePage : Page
{
    protected override void SavePageStateToPersistenceMedium(object viewState)
    {
        string key = "VS_" + Session.SessionID + "_" + Request.Path;
        Session[key] = viewState;
        ClientScript.RegisterHiddenField("__VIEWSTATE_KEY", key);
    }
    
    protected override object LoadPageStateFromPersistenceMedium()
    {
        string key = Request.Form["__VIEWSTATE_KEY"];
        if (!string.IsNullOrEmpty(key) && Session[key] != null)
        {
            object state = Session[key];
            Session.Remove(key); // 一次性使用
            return state;
        }
        return base.LoadPageStateFromPersistenceMedium();
    }
}

4. 選擇性儲存 (ControlState vs ViewState)


public class CriticalControl : Control
{
    // 必須持久化的關鍵資料 (如分頁索引、排序欄位) -> ControlState
    protected override object SaveControlState()
    {
        return new Pair(base.SaveControlState(), CurrentPageIndex);
    }
    
    protected override void LoadControlState(object savedState)
    {
        Pair state = (Pair)savedState;
        base.LoadControlState(state.First);
        CurrentPageIndex = (int)state.Second;
    }
    
    // 一般 UI 狀態 -> ViewState (可關閉)
    public string UserPreference
    {
        get => ViewState["UserPref"] as string;
        set => ViewState["UserPref"] = value;
    }
}

視圖狀態除錯與監控

追蹤視圖狀態大小


protected override void Render(HtmlTextWriter writer)
{
    base.Render(writer);
    
    // 開發環境監控
    if (HttpContext.Current.IsDebuggingEnabled)
    {
        long viewStateSize = 0;
        if (Page.Request.Form["__VIEWSTATE"] != null)
            viewStateSize = Page.Request.Form["__VIEWSTATE"].Length;
        
        writer.Write($"");
    }
}

檢視視圖狀態內容工具


// 開發工具:解碼 ViewState
public static class ViewStateDecoder
{
    public static string Decode(string base64ViewState)
    {
        // 使用 LosFormatter 反序列化
        LosFormatter formatter = new LosFormatter();
        object state = formatter.Deserialize(Convert.FromBase64String(base64ViewState));
        return ObjectDumper.Dump(state);
    }
}

常見陷阱與解決方案

問題原因解決方案
視圖狀態過大導致效能差大量資料存入 ViewState (如 DataTable)僅存儲必要資料 (ID、索引),大量資料用 Session/Cache/資料庫
動態控件視圖狀態遺失動態控件未在 OnInit/LoadViewState 前重建確保 CreateChildControls 在 LoadViewState 前執行
複雜物件狀態不持久化未實作 IStateManager 或未呼叫 TrackViewState複雜屬性物件必須實作 IStateManager,且在 getter 中同步 TrackViewState
跨頁面 ViewState 衝突相同控件 ID 在不同頁面產生相同 ViewState 結構使用命名空間前綴鍵值,或實作 IPostBackDataHandler
安全性風險 (資料竄改)ViewState 未加密/驗證啟用

測試驗證範例


<%@ Page Language="C#" Trace="true" %>





啟用 Trace="true" 觀察 Control Tree 區域的 ViewState 欄位,驗證各屬性是否正確持久化。

總結

自訂視圖狀態管理核心原則:

  1. 簡單型別:直接使用 ViewState[key],框架自動處理
  2. 複雜物件:實作 IStateManager,內部使用 StateBag
  3. 父控件協調:覆寫 SaveViewState/LoadViewState 串聯子物件狀態
  4. 追蹤同步:IsTrackingViewState 觸發子物件 TrackViewState()
  5. 效能優化:關閉不需要的 ViewState、壓縮、伺服器端儲存、ControlState 分離

掌握這些技術,即可精確控制控件狀態持久化行為,構建高效能、安全、可擴展的 WebForms 應用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved