Monday, July 21, 2008

ViewState - A Quick Users Guide

There are a lot of tutorials on ViewState. I have read quite a few of them, and this is meant to expand upon them by condensing information in an easy to understand format. Hope it helps.

GamePlan
I want to show you what ViewState can be used to store - with little to zero work on your part. I will start with the really simple stuff - then step it up a few times to finally end with Composite Controls (a custom control you make from other controls) with constructors.

No Dynamics - All Static Content
  • The first thing you will be told about viewstate is that declarative syntax is automatically stored. Example :

--------------------------------
ASPX
<asp:dropdownlist id="ddl" runat="server" enableviewstate="true">
<asp:listitem text="1" value="1"></asp:listitem>
<asp:listitem text="2" value="2"></asp:listitem>
<asp:listitem text="3" value="3"></asp:listitem>
<asp:listitem text="4" value="4"></asp:listitem></asp:dropdownlist>
--------------------------------


This DDL will always be visible on the page. If you choose an item, and cause a postback - your item will still be selected. This is the first thing everyone see's - which leads a lot of people to get the illusion ViewState is 'magical'. This isn't true, it's easy to understand. I am not covering how it works right now, just how to use it.

Programmatically Added Controls
  • Here it gets a tad more interesting. I am programmatically adding a DDL to the page. The key is - if you add something after the page has been sent to the client once (this is a tad misleading, but its the really simple explanation) - then you must re-add it to the page EVERY time


--------------------------------
ASPX
<asp:panel id="panel" runat="server" enableviewstate="true">
</asp:panel>

Code Behind
protected void Page_Load(object sender, EventArgs e)
{
add_ddl();
}

private void add_ddl()
{
DropDownList d = new DropDownList();
panel.Controls.Add(d);

if (d.Items.Count == 0)
{
d.Items.Add("First");
d.Items.Add("Second");
d.Items.Add("Third");
d.EnableViewState = true;
}
}
--------------------------------
  • So - the nifty part is that via some .NET naming stuff and fancy tricks - Your settings will be restored - even though it is technically a 'new' control! What this means is that you can programmatically add two controls, change their settings, and cause a postback - and re-add new controls in the same place - and they will still have the settings you chose before!

Composite Control Without Special Constructor
So - this will show you how to build a simple composite control. The nice thing is that this example is really the exact same as the last one - just with a composite control. All you have to do is (assuming you add your composite control at run-time, aka in the Code Behind and not in the aspx) re-add your control and any values you choose before will be automatically persisted. Pretty neat.

--------------------------------
Code Behind
{ . . .

protected void Page_Load(object sender, EventArgs e)
{
add_ddl();

panel.Controls.Add(new Add_Composite_Control());
}

. . . }

public class Add_Composite_Control:CompositeControl
{
private DropDownList dl;

protected override void CreateChildControls()
{
Controls.Clear();
CreateControlHierarchy();
ClearChildViewState();
}

protected virtual void CreateControlHierarchy()
{
dl = new DropDownList();

dl.Items.Add("Custom 1");
dl.Items.Add("Custom 2");
dl.Items.Add("Custom 3");

Controls.Add(dl);
}
}
--------------------------------
  • Once again, the nifty thing here is that .NET naming classes can "remember" the right names for everything, so your changes persist across postbacks. So - try it out. Add two of these controls to the page, and vary their values. Cause a Postback - and the controls will be re-created with the values they had before.


Composite Control With Special Constructor
  • This is the hardest example I am going to show you. It involves manually setting stuff in the ViewState. Up until now, we have just had to re-add controls and everything has been quite easy.
  • We need to set ViewState variables because we need to re-add the controls to the page. And without the constructor arguments, we cannot re-add anything. Note that we do not have to remember what value was selected, or anything like that. We are still using ViewState to it's full advantage. All we need to be able to do is re-add the controls
--------------------------------
ASPX
<asp:Panel ID="panel1" runat="server">
</asp:Panel>

<asp:Button Text="Add Control" ID="btn2" runat="server" OnClick="Add_Control" />


Code Behind

{ . . .

protected void Page_Load(object sender, EventArgs e)
{

if (ViewState["storage"] != null)
{
// Create the regex
string variables1 = (string)ViewState["storage"];
Regex reg = new Regex(@"\|(?.+?),(?.+?);");
Match m = reg.Match(variables1);

// extract the variables
while (m.Success)
{
string varname = m.Groups["name"].ToString();
string id = m.Groups["id"].ToString();
panel1.Controls.Add(new Log_Format_Variable(varname, id));
m = m.NextMatch();
}
}
}
protected void Add_Control(object obj, EventArgs e)
{
Random r = new Random();
int rand = r.Next(1,3);

if (rand == 1)
{
panel1.Controls.Add(new Log_Format_Variable("var_one", "1"));
ViewState["storage"] += "|var_one,1;";
}
if (rand == 2)
{
panel1.Controls.Add(new Log_Format_Variable("var_two", "2"));
ViewState["storage"] += "|var_two,2;";
}
if (rand == 3)
{
panel1.Controls.Add(new Log_Format_Variable("var_three", "3"));
ViewState["storage"] += "|var_three,3;";
}
}

. . . }

public class Log_Format_Variable : CompositeControl
{
private DropDownList d;
private string var_ID;
private string varname;

public Log_Format_Variable(string varname, string var_ID)
{
this.var_ID = var_ID;
this.varname = varname;
}

protected override void CreateChildControls()
{
Controls.Clear();
CreateControlHierarchy();
ClearChildViewState();
}
protected virtual void CreateControlHierarchy()
{
Label l = new Label();
l.Text = "Function for " + varname;

LiteralControl lc1 = new LiteralControl(" ");

d = new DropDownList();
d.Items.Add(new ListItem("Sum", "SUM"));
d.Items.Add(new ListItem("Average", "AVG"));
d.Items.Add(new ListItem("Max", "MAX"));
d.Items.Add(new ListItem("Min", "MIN"));

LiteralControl lc2 = new LiteralControl("
");

this.Controls.Add(l);
this.Controls.Add(lc1);
this.Controls.Add(d);
this.Controls.Add(lc2);
}
}

So, basically all I am doing extra here is saving the information needed to re-call the constructors. I use a small schema to save the constructor info - the "|varname,var_id;" scheme makes it simple for me to get the info back out of the ViewState, but this is really not necessary - there are many better ways to store the info. The Add_Control just lets me add a few random comtrols, and it does not matter if two controls have the same constructor information. Every CompositeControl implements a Naming interface, which means that all things belonging to that control have a unique prefix. Hence, if two controls have the same constructor info - they are still regarded as two separate entities. So - try this out. You can create as many of these as you would like, and they will all be able to remember their values across subsequent postbacks.

WrapUp
Here is the source code for the entire project - which includes all of the control types listed above.


Things to note
  • This probably did not help you understand at all why viewstate works. Please read up on that to get the full picture
  • To cause a postback, just add a button to your ASPX page. Don't assign it an OnClick value, and it will simply postback and do nothing else
  • ViewState information is saved on the CLIENT side - and can thus be victim to a few things. Notably, someone can tamper with it. It is weakly encrypted and can be easily changed.
  • Again about the client side - this means larger pages, larger page load times, etc. Long story short, don't store a lot of information in the ViewState.
  • ViewState is enabled by default. All of my enableviewstate="true" are just for clarity. You don't need them

Please let me know if I have goofed anything or could have done something better. This is not meant to be an in-depth guide to understanding ViewState, but rather a quick-flip guide to using it easily.

Hamy

References

Understanding ASP.NET ViewState by Scott Mitchell
Control Building and ViewState Lesson for the Day
Binary Fortress ViewState Helper Tool
There is at least one significant other site that I sadly cannot remember the name of. There are also a significant number of small helper sites.

No comments: