In this article, I would only cover the basics of view state. If you do not know what view state is, it is the medium of containing data to persist when the user posts back to the same page. This concept is not new to classic ASP at all. In classic ASP, persisting data for reuse on the same page was a very tedious task - never mind the fact it was normally in clear text. ASP.NET simplifies that process of persisting data so it is as simple to use as the Application state. View state is used by server controls to persist its data across different requests - it is not designed to persist server controls itself, nor its sub controls.
I am not going to write a long talk about "What is view state?" Instead, you can read a very well explained article by Paul Wilson, title “View State: All You Wanted to Know”.
Just a recap of the only rule of view state - what view state is optimized for:
-
Objects (that are one of the following).
-
Integers.
-
Strings.
-
Floats.
-
Decimals.
-
Objects.
-
Pair class containing the above data types or Triplet class.
-
Triplet class containing the above data types.
-
Arrays of the data types stated above.
-
ArrayList with the values being a type of data types listed above.
-
A StateBag containing the values of the data types listed above.
You can create custom optimized classes for ViewState by implementing IStateManager interface but essentially, you will have to drill down your basic string data type. Essentially, a normal XYZ class is not optimal to be put into ViewState without the implementation of the IStateManager interface - it can be done but heavy work will be required to be completed by ASP.NET to be serialized.
Also note that if you are planning to persist a structure, you will have to add the SerializableAttribute attribute to the actual structure.
The normal suggestion of making your properties persist in view state is by making your properties follow the same type of format:
// [C#]
public string FooString {
get {
string value = (string)ViewState["FooString"];
return (value != null) ? (string)value : String.Empty;
}
set { ViewState["FooString"] = value; }
}
' [VB.NET]
Public Property FooString() As String
Get
Dim value As String = CStr(ViewState("FooString"))
If Not (value Is Nothing) Then
Return CStr(value)
Else
Return [String].Empty
End If
End Get
Set
ViewState("FooString") = value
End Set
End Property
Tiresome isn't it!? But I have a better solution because I am just as lazy like you. Here is the basic "magic" code that will needed by all controls that is going to follow our lazy route:
(Edit: Marvin Slayton gave some feedback on the original source. He managed to pick up a few bugs that the original source allowed to slip through for posting back to the server. To reproduce the error, view state keys that were set before the Init event caused the keys that were later added via the LoadViewState method to throw an exception. He was even kind enough to provide the fix and I have further modified the fix to provide consinstency.)
// [C#]
private const string BaseStateKey = "DefaultViewState";
private Hashtable StateTable = new Hashtable();
protected internal void SaveToStateTable(string key, object value) {
if (StateTable.ContainsKey(key)) StateTable[key] = value.ToString();
else StateTable.Add(key, value.ToString());
}
protected internal object LoadFromStateTable(string key, object defaultValue) {
if (StateTable.ContainsKey(key))
return (string)StateTable[key];
else {
if ((defaultValue.GetType() != typeof(string)) &&
(defaultValue.GetType() != typeof(ValueType))) {
StateTable.Add(key, defaultValue);
}
return defaultValue;
}
}
protected override object SaveViewState() {
string[] keys = new string[StateTable.Count];
ArrayList indices = new ArrayList();
ArrayList state = new ArrayList();
StateTable.Keys.CopyTo(keys, 0);
indices.Add(BaseStateKey);
state.Add(base.SaveViewState());
foreach (string key in keys) {
indices.Add(key);
state.Add(StateTable[key]);
}
return new Pair(indices, state);
}
protected override void LoadViewState(object state) {
if (state == null) return;
ArrayList indices = ((Pair)state).First as ArrayList;
ArrayList values = ((Pair)state).Second as ArrayList;
for (int i = 0; i < indices.Count; i++) {
if (indices[i].ToString() == BaseStateKey)
base.LoadViewState(values[i]);
else
SaveToStateTable(indices[i].ToString(), values[i]);
}
}
' [VB.NET]
Private Const BaseStateKey As String = "DefaultViewState"
Private StateTable As New Hashtable()
Friend Protected Sub SaveToStateTable(key As String, value As Object)
If StateTable.ContainsKey(key) Then
StateTable(key) = value.ToString()
Else
StateTable.Add(key, value.ToString())
End If
End Sub
Friend Protected Function LoadFromStateTable(key As String, _
defaultValue As Object) As Object
If StateTable.ContainsKey(key) Then
Return CStr(StateTable(key))
Else
If defaultValue.GetType() <> GetType(String) And _
defaultValue.GetType() <> GetType(ValueType) Then
StateTable.Add(key, defaultValue)
End If
Return defaultValue
End If
End Function
Protected Overrides Function SaveViewState() As Object
Dim keys(StateTable.Count) As String
Dim indices As New ArrayList()
Dim state As New ArrayList()
StateTable.Keys.CopyTo(keys, 0)
indices.Add(BaseStateKey)
state.Add(MyBase.SaveViewState())
For Each key As String In keys
indices.Add(key)
state.Add(StateTable(key))
Next
Return New Pair(indices, state)
End Function
Protected Overrides Sub LoadViewState(state As Object)
If state Is Nothing Then
Return
End If
Dim indices As ArrayList = CType(CType(state, Pair).First, ArrayList)
Dim values As ArrayList = CType(CType(state, Pair).Second, ArrayList)
For i As Integer = 0 To indices.Count - 1
If indices(i).ToString() = BaseStateKey Then
MyBase.LoadViewState(values(i))
Else
SaveToStateTable(indices(i).ToString(), values(i))
End If
Next
End Sub
With this code, we will save the default view state with the custom view state. We also added the feature that advanced data types will be automatically added to the view state bin if it does not exist in the bin. If it does, any changes to the data inside itself will remain persistent. Now I am going to demonstrate the same functionality that the code before my magic hand was used with the new, improved, lazy format:
// [C#]
public string FooString {
get { return (string)LoadFromStateTable("FooString", String.Empty); }
set { SaveToStateTable("FooString", value); }
}
' [VB.NET]
Public Property FooString() As String
Get
Return CStr(LoadFromStateTable("FooString", [String].Empty))
End Get
Set
SaveToStateTable("FooString", value)
End Set
End Property
For more advanced data types such as classes, you can still use this lazy method. Instead of defining a default value such as a "0", you would create a new instance of that data type (by its constructor). Please remember, for optimal performance, ensure that the class implements IStateManager interface for the view state to deserialize the object a lot quicker.
I will discuss further into view state and handling it at a later article in the series. Depending on the feedback I receive and the topics and content of the threads at the www.asp.net/forums/, will determine the depth that it will be discussed in the future.