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);
}
}
}
[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();
}
}
// 控件層級
// 頁面層級
<%@ Page EnableViewState="false" %>
// 程式碼層級
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
this.EnableViewState = false; // 動態關閉
}
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();
}
}
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();
}
}
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 欄位,驗證各屬性是否正確持久化。
自訂視圖狀態管理核心原則:
ViewState[key],框架自動處理IStateManager,內部使用 StateBagSaveViewState/LoadViewState 串聯子物件狀態IsTrackingViewState 觸發子物件 TrackViewState()掌握這些技術,即可精確控制控件狀態持久化行為,構建高效能、安全、可擴展的 WebForms 應用。