TRULY Understanding Viewstate
UPDATE: THIS POST HAS BEEN MOVED TO MY NEW BLOG! Please redirect your bookmarks here:
http://weblogs.asp.net/infinitiesloop/archive/2006/08/03/Truly-Understanding-Viewstate.aspx
If you read this blog at all, by now you've got to be sick of me talking about ViewState. I'm sorry, but its becoming increasingly clear to me that ViewState is a very misunderstood animal. I would like to help put an end to the madness by attempting to explain exactly how the ViewState mechanism works, from beginning to end, and from many different use cases, such as declared controls vs. dynamic controls.
There are a lot of great articles out there that try to dispel the myths about ViewState. You might say this is like beating a dead horse (where ViewState is the horse, and the internet is the assailant). But this horse isn't dead, let me tell you. No, he's very much alive and he's stampeding through your living room. We need to beat him down once again. No horses were harmed during the authoring of this article.
It's not that there's no good information out there about ViewState, it's just all of them seem to be lacking something, and that is contributing to the community's overall confusion about ViewState. For example, one of the key features that is important to understand about ViewState is how it tracks dirtiness. Yet, here is a very good, in-depth article on ViewState that doesn't even mention it! Then there's this W3Schools article on ViewState that seems to indicate that posted form values are maintained via ViewState, but that's not true. Don't believe me? Disable ViewState on that textbox in their example and run it again). And it's the #1 Google Search Result for "ASP.NET ViewState". Can you believe that? Ok so these are all third party sites. Microsoft articles wouldn't be wrong, would they? No. But, they aren't extremely in depth either. For example, here is ASP.NET Documentation on MSDN that describes how Controls maintain state across postbacks. The documentation isn't wrong per say, but it makes a statement that isn't entirely correct:
"If a control uses ViewState for property data instead of a private field, that property automatically will be persisted across round trips to the client.
That seems to imply that anything you shove into the ViewState StateBag will be round-tripped in the client's browser. NOT TRUE!
So it's really no wonder there is so much confusion on ViewState. There is no where I've found on the internet that has a 100% complete and accurate explanation of how it works! The best article I have ever found is this one by Scott Mitchell. There's nothing that is wrong or even ambiguous in that article, however, it is incomplete. It does not explain the relationship of controls and their child controls when it comes to initialization and ViewState Tracking, and it is this point alone that causes a bulk of the mishandlings of ViewState, at least in the experiences I've had.
So the point of this article will be to first give a complete understanding of how ViewState basically functions, from beginning to end, hopefully filling in the holes that many other articles have, but without going into a lot of technical detail on things like LosFormatting, for example, because there are plenty of sites out there that do an awesome job of that already. After a complete explanation of the entire ViewState process, I will go into some examples of how developers typically misuse ViewState, usually without even realizing it, and how to fix it.
First let me explain why understanding ViewState to it's core is so important:
- Leakage of sensitive data
- ViewState Attacks - aka the Jedi Mind Trick -- *waves hand* that plasma tv is for sale for $1.00
- Poor performance - even to the point of NO PERFORMANCE
- Poor scalability - how many users can you handle if each is posting 50k of data every request?
- Overall poor design
- Headache, nausea, dizziness, and irreversible frilling of the eyebrows.
MISUNDERSTANDING OF VIEWSTATE WILL LEAD TO...
If you develop an ASP.NET Application and you don't take ViewState seriously, this could happen to you:
The ViewState form field on a prominent asp.net community site. | ViewState will add your web app's distinctiveness to it's own. Performance is futile. |
I could go on but that is the gist of it. Now lets move on by starting back from the beginning:
- Stores values per control by key name, like a hashtable
- Tracks changes to a ViewState value's initial state
- Serializes and Deserializes saved data into a hidden form field on the client
- Automatically restores data on postbacks
WHAT DOES VIEWSTATE DO?
This is a list of ViewState's main jobs. Each of these jobs serves a very distinct purpose. Next we'll learn exactly how it fulfills those jobs.
Even more important than understanding what it does, is understanding what it does NOT do:
- Automatically retain state of class variables (private, protected, or public)
- Remember any state information across page loads (only postbacks) (that is unless you customize how the data is persisted)
- Remove the need to repopulate data on every request
- ViewState is not responsible for the population of values that are posted such as by TextBox controls (although it does play an important role)
- Make you coffee
WHAT DOESN'T VIEWSTATE DO?
While ViewState does have one overall purpose in the ASP.NET Framework, it's four main roles in the page lifecycle are quite distinct from each other. Logically, we can separate them and try to understand them individually. It is often the mishmash of information on ViewState that confuses people. Hopefully this breaks it down into more bite size nuggets. Mmmm... ViewState Nuggets.
1. VIEWSTATE STORES VALUES
If you've ever used a hashtable, then you've got it. There's no rocket science here. ViewState has an indexer on it that accepts a string as the key and any object as the value. For example:
ViewState["Key1"] = 123.45M; // store a decimal value
ViewState["Key2"] = "abc"; // store a string
ViewState["Key3"] = DateTime.Now; // store a DateTime
Actually, "ViewState" is just a name. ViewState is a protected property defined on the System.Web.UI.Control class, from which all server controls, user controls, and pages, derive from. The type of the property is System.Web.UI.StateBag. Strictly speaking, the StateBag class has nothing to do with ASP.NET. It happens to be defined in the System.Web assembly, but other than it's dependency on the LosFormatter, also defined in System.Web.UI, there's no reason why the StateBag class couldn't live along side ArrayList in the System.Collections namespace. In fact, I've often wondered why Microsoft did not do just that since the StateBag has many useful features that the other collections are lacking.
In practice, Server Controls utilize ViewState as the backing store for most, if not all their properties. This is true of almost all Microsoft's built in controls (ie, label, textbox, button). This is important! You must understand this about controls you are using. Read that sentance again. I mean it... here it is a 3rd time: SERVER CONTROLS UTILIZE VIEWSTATE AS THE BACKING STORE FOR MOST, IF NOT ALL THEIR PROPERTIES. Depending on your background, when you think of a traditional property, you might imagine something like this:
What is important to know here is that this is NOT what most properties on ASP.NET controls look like. Instead, they use the ViewState StateBag, not a private instance variable, as their backing store:public string Text
{
get { return _text; }
set { _text = value; }
}
public string Text
{
get { return (string) ViewState["Text"]; }
set { ViewState["Text"] = value; }
}
And I can't stress it enough -- this is true of almost ALL PROPERTIES, even STYLES (actually, Styles do it by implementing IStateManager, but essentially they do it the same way). When writing your own controls it would usually be a good idea to follow this pattern, but thought should first be put into what should and shouldn't be allowed to be dynamically changed on postbacks. But I digress -- that's a different subject.
It is also important to understand how DEFAULT VALUES are implemented using this technique. When you think of a property that has a default value, in the traditional sense, you might imagine something like the following:
The default value is the default because it is what is returned by the property if no one ever sets it. How can we accomplish this when ViewState is being used as the private backing? Like this:public class MyClass
{
private string _text = "Default Value!";
public string Text
{
get { return _text; }
set { _text = value; }
}
}
Like a hashtable, the StateBag will return null as the value behind a key if it simply doesn't contain an entry with that key. So if the value is null, it has not been set, so return the default value, otherwise return whatever the value is. For you die-hards out there -- you may have detected a difference in these two implementations. In the case of ViewState backing, setting the property to NULL will result in resetting the property back to it's default value. With a "regular" property, setting it to null means it will simply be null. Well, that is just one reason why ASP.NET always tends to use String.Empty ("") instead of null. It's also not very important to the built in controls because basically all of their properties that can be null already are null by default. All I can say is keep this in mind if you write your own controls.public string Text
{
get
{
return ViewState["Text"] == null ?
"Default Value!" :
(string) ViewState["Text"];
}
set { ViewState["Text"] = value; }
}
And finally, as a footnote really, while this property-backing usage of the ViewState StateBag is how the StateBag is typically used, it isn't limited to just that. As a control or page, you can access you're own ViewState StateBag at any time for any reason, not just in a property. It is sometimes useful to do so in order to remember certain pieces of data across postbacks, but that too is another subject.
2. VIEWSTATE TRACKS CHANGES
Have you ever set a property on a control and then somehow felt... dirty? I sure have. In fact, after a twelve-hour day of setting properties in the office, I become so filthy my wife refuses to kiss me unless I'm holding flowers to mask the stench. I swear!
Ok so setting properties doesn't really make you dirty. But it does make the entry in the StateBag dirty! The StateBag isn't just a dumb collection of keys and values like a Hashtable (please don't tell Hashtable I said that, he's scarey). In addition to storing values by key name, the StateBag has a TRACKING ability. Tracking is either on, or off. Tracking can be turned on by calling TrackViewState(), but once on, it cannot be turned off. When tracking is ON, and ONLY when tracking is ON, any changes to any of the StateBag's values will cause that item to be marked as "Dirty". StateBag even has a method you can use to detect if an item is dirty, aptly named IsItemDirty(string key). You can also manually cause an item to be considered dirty by calling SetItemDirty(string key). To illustrate, lets assume we have a StateBag that is not currently tracking:
stateBag.IsItemDirty("key"); // returns false
stateBag["key"] = "abc";
stateBag.IsItemDirty("key"); // still returns false
stateBag["key"] = "def";
stateBag.IsItemDirty("key"); // STILL returns false
stateBag.TrackViewState();
stateBag.IsItemDirty("key"); // yup still returns false
stateBag["key"] = "ghi";
stateBag.IsItemDirty("key"); // TRUE!
stateBag.SetItemDirty("key", false);
stateBag.IsItemDirty("key"); // FALSE!
Basically, tracking allows the StateBag to keep track of which of it's values have been changed since TrackViewState() has been called. Values that are assigned before tracking is enabled are not tracked (StateBag turns a blind eye). It is important to know that any assignment will mark the item as dirty -- even if the value given matches the value it already has!
stateBag["key"] = "abc";
stateBag.IsItemDirty("key"); // returns false
stateBag.TrackViewState();
stateBag["key"] = "abc";
stateBag.IsItemDirty("key"); // returns true
ViewState could have been written to compare the new and old values before deciding if the item should be dirty. But recall that ViewState allows any object to be the value, so you aren't talking about a simple string comparison, and the object doesn't have to implement IComparable so you're not talking about a simple CompareTo either. Alas, because serialization and deserialization will be occuring, an instance you put into ViewState won't be the same instance any longer after a postback. Microsoft decided rightfully so that this kind of comparison is not important for ViewState to do it's job.
So that's tracking in a nutshell. But you might wonder why StateBag would need this ability in the first place. Why on earth would anyone need to know only changes since TrackViewState() is called? Why wouldn't they just utilize the entire collection of items?
This one point seems to be at the core of all the confusion on ViewState. I have interviewed countless professionals, sometimes with years and years of ASP.NET experience logged in their resumes, who have failed miserably to prove to me that they understand this point. Actually, I have yet to interview a single candidate who has. And let me just say, if I interviewed you and you did get it right, and it was entirely up to me, your odds of being hired would go through the roof.
First, to truly understand why Tracking is needed, you will need to understand a little bit about how ASP.NET sets up declarative controls. Declarative controls are controls that are defined in your ASPX or ASCX form. Here:
I do declare that this label is declared on your form. The next thing we need to make sure you understand is ASP.NET's ability to wire up declared attributes to control properties. When ASP.NET parses the form, and finds a tag with runat=server, it creates an instance of the specified control. The variable name it assigns the instance to is based on the ID you assigned it (by the way, many don't realize that you don't have to give a control an ID at all, ASP.NET will use an automatically generated ID. Not specifying an ID has advantages, but that is a different subject). But that's not all it does. The control's tag may contain a bunch of attributes on it. In our label example up above, we have a "Text" attribute, and it's value is "Hello World". Using reflection, ASP.NET is able to detect whether the control has property by that name, and if so, sets its value to the declared value. Obviously the attribute is declared as a string (hey, its stored in a text file after all), so if the property it maps to isn't of type string, it must figure out how to convert the given string into the correct type, before calling the property setter. How it does that my friend is also an entirely different topic (it involves TypeConverters and static Parse methods). Suffice it to say it figures it out, and calls the property setter with the converted value.<asp:Label id="lbl1" runat="server" Text="Hello World" />
Recall that all-important statement from the first role of the StateBag. Here it is again: Server Controls utilize ViewState as the backing store for most, if not all their properties. That means when you declare an attribute on a server control, that value is usually ultimately stored as an entry in that control's ViewState StateBag. Now recall how tracking works. Remember that if the StateBag is "tracking", then setting a value to it will mark that item as dirty. If it isn't tracking, it won't be marked dirty. So the question is -- when ASP.NET calls the SET on the PROPERTY that corresponds to the ATTRIBUTE that is DECLARED on the control, is the StateBag TRACKING or isn't it? The answer is no it is not tracking, because tracking doesn't begin until someone calls TrackViewState() on the StateBag, and ASP.NET does that during the OnInit phase of the page/control lifecycle.
This little trick ASP.NET uses to populate properties allows it to easily detect the difference between a declaratively set value and dynamically set value. If you don't yet realize why that is important, please keep reading.
3. SERIALIZATION AND DESERIALIZATION
Aside from how ASP.NET creates declarative controls, the first two capabilities of ViewState we've discussed so far have been strictly related to the StateBag class (how it's similar to a hashtable, and how it tracks dirty values). Here is where things get bigger. Now we will have to start talking about how ASP.NET uses the ViewState StateBag's features to make the (black) magic of ViewState happen.
If you've ever done a "View Source" on an ASP.NET page, you've no doubt encountered the serialization of ViewState. You probably already knew that ViewState is stored in a hidden form field aptly named _VIEWSTATE as a base64 encoded string, because when anyone explains how ViewState works, that's usually the first thing they mention.
A brief aside -- before we understand how ASP.NET comes up with this single encoded string, we must understand the hierarchy of controls on the page. Many developers with years of experience still don't realize that a page consists of a tree of controls, because all they work on are ASPX pages, and all they need to worry about are controls that are directly declared on those pages... but controls can contain child controls, which can contain their own child controls, etc. This forms a tree of controls, where the ASPX page itself is the root of that tree. The 2nd level is all the controls declared at the top level in the ASPX page (usually that consists of just 3 controls -- a literal control to represent the content before the form tag, a HtmlForm control to represent the form and all its child controls, and another literal control to represent all the content after the close form tag). On the 3rd level are all the controls contained within those controls (ie, controls that are declared within the form tag), and so on and so forth.
Each one of the controls in the tree has it's very own ViewState -- it's very own instance of a StateBag. There's a protected method defined on the System.Web.UI.Control class called SaveViewState. It returns type 'object'. The implementation for Control.SaveViewState is to simply pass the call along to the Control's StateBag (it too has a SaveViewState() method). By calling this method recursively on every control in the control tree, ASP.NET is able to build another tree that is structured not unlike the control tree itself, except instead of a tree of controls, it is a tree of data. The data at this point is not yet converted into the string you see in the hidden form field, it's just an object tree of the data to be saved.
Here is where it finally comes together... are you ready?
When the StateBag is asked to save and return it's state (StateBag.SaveViewState()), it only does so for the items contained within it that are marked as Dirty. That is why StateBag has the tracking feature. That is the only reason why it has it. And oh what a good reason it is -- StateBag could just process every single item stored within it, but why should data that has not been changed from it's natural, declarative state be persisted? There's no reason for it to be -- it will be restored on the next request when ASP.NET reparses the page anyway (actually it only parses it once, building a compiled class that does the work from then on).
Despite this smart optimization employed by ASP.NET, unnecessary data is still persisted into ViewState all the time due to misuse. I will get into examples that demonstrate these types of mistakes later on.
POP QUIZ
If you've read this far, congratulations, I am rewarding you with a pop quiz. Aren't I nice? Here it is:
Let's say you have two nearly identical ASPX forms: Page1.aspx and Page2.aspx. Contained within each page is just a form tag and a label, like so:
<form id="form1" runat="server">
<asp:Label id="label1" runat="server" Text="" />
</form>
They are identical except for one minor difference. In Page1.aspx, we shall declare the label's text to be "abc":
<asp:Label id="label1" runat="server" Text="abc" />
...And on Page2.aspx, we shall declare the label's text to be something much longer (the preamble to the Constitution of the United States of America):
Imagine you browse to Page1.aspx, you will see "abc" and nothing more. Then you use your browser to view the HTML source of the page. You will see the infamous hidden _VIEWSTATE hidden field with encoded data in it. Note the size of that string. Now you browse to Page2.aspx, and you see the preamble. You use your browser to view the HTML source once again, and you note the size of the encoded _VIEWSTATE field.<asp:Label id="label1" runat="server" Text="We the people of the
United States, in order to form a more perfect union,
establish justice, insure domestic tranquility, provide
for the common defense, promote the general welfare,
and secure the blessings of liberty to ourselves and
our posterity, do ordain and establish this Constitution
for the United States of America." />
The question is: Are the two sizes you noted the same, or are they different?
Before we get to the answer, lets make it a little bit more involved. Lets say you also put a button next to the label (on each page):
<asp:Button id="button1" runat="server" Text="Postback" />
There is no code in the click event handler for this button, so clicking on it doesn't do anything except make the page flicker. With this new button in place, you repeat the experiment, except this time when browsing to each page, you click the Postback button before looking at the HTML source. So the question is once again...
Are the encoded ViewState values the same, or different?
The correct answer to the first part the question is THEY ARE THE SAME! They are the same because in neither of the two viewstate strings are any data related to the label at all. If you understand ViewState, this is obvious. The Text property of the label is set to the declared value before it's ViewState is being tracked. That means if you were to check the dirty flag of the Text item in the StateBag, it would not be marked dirty. StateBag ignores items that aren't dirty when SaveViewState() is called, and it is the object it returns that is serialized into the hidden _VIEWSTATE field. Therefore, the text property is not serialized. Since the Text is not serialized in either case, and the forms are identical in every other way, the sizes of the encoded viewstates on each page must be the same. In fact, no matter how large or small of a string you stuff into that text attribute, the size will remain the same.
The correct answer to the second part is again, THEY ARE THE SAME! In order for data to be serialized, it must be marked as dirty. In order to be marked as dirty, it's value must be set after TrackViewState() is called. But even when we perform a postback, ASP.NET recreates and populates the server controls in the same way. The Text property is still set to it's declared value just like it was in the first request. No other code is setting the text property, so there's no way the StateBag item could become dirty, even on a postback. Therefore, the sizes of the encoded viewstates on each page after a postback must be the same.
So now we understand how ASP.NET determines what data needs to be serialized. But we don't know how it is serialized. That topic is outside the scope of this article (are you missing an assembly reference?), so if you're really interested in how it works, read up on the LosFormatter.
Finally on this topic is DESERIALIZATION. Obviously all this fancy dirty tracking and serialization wouldn't do any good if we couldn't get the data back again. That too is outside the scope of this article, but suffice it to say the process is just the reverse. ASP.NET rebuilds the object tree it serialized by reading the posted _VIEWSTATE form value and deserializing it with the LosFormatter.
4. AUTOMATICALLY RESTORES DATA
This is last on our list of ViewState features. It is tempting to tie this feature in with Deserialization above, but it is not really part of that process. ASP.NET deserializes the ViewState data, and THEN it repopulates the controls with that data. Many articles out there confuse these two processes. Defined on System.Web.UI.Control (again, the class that every control, user control, and page derive from) is a LoadViewState() method which accepts an object as parameter. This is the opposite of the SaveViewState() method we already discussed, which returns an object. Like SaveViewState(), LoadViewState() simple forwards the call on to it's StateBag object, calling LoadViewState on it. The StateBag then simply repopulates it's key/object collection with the data in the object. In case you are wondering, the object it is given is a System.Web.UI.Pair class, which is just a simple type with a First and Second field on it. The "First" field is an ArrayList of key names, and the "Second" field is an ArrayList of values. So StateBag just iterates over the lists, calling this.Add(key, value) for each item. The important thing to realize here is that the data it was given via LoadViewState() are only items that where marked dirty on the previous request. Prior to loading the ViewState items, the StateBag may already have values in it. Those values may be from declarative properties like we discussed, but they may also be values that were explicitly set by the developer prior to the LoadViewState call. If one of the items passed into LoadViewState already exists in the StateBag for some reason, it will be overwritten. That right there is the magic of automatic state management. When the page first begins to load during a postback (even prior to initialization), all the properties are set to their declared natural defaults. Then OnInit occurs. During the OnInit phase, ASP.NET calls TrackViewState() on all the StateBags. Then LoadViewState() is called with the deserialized data that was dirty from the previous request. The StateBag calls Add(key, value) for each of those items. Since the StateBag is tracking at this point, the value is marked dirty, so that it may be persisted once again for the next postback. Brilliant!
Whew. Now you are an expert on ViewState management.
IMPROPER USE OF VIEWSTATE
Now that we know exactly how ViewState works, we can finally begin to understand the problems that arise when it is used improperly. In this section I will describe cases that illustrate how a lot of ASP.NET developers misuse ViewState. But these aren't just obvious mistakes. Some of these will illustrate nuances about ViewState that will give you an even deeper understanding of how it all fits together.
- Forcing a Default
- Persisting static data
- Persisting cheap data
- Initializing child controls programmatically
- Initializing dynamically created controls programmatically
CASES OF MISUSE
1. Forcing a Default
This is one of the most common misuses, and it is also the easiest to fix. The fixed code is also usually more compact than the wrong code. Yes, doing things the right way can lead to less code. Imagine that.
This usually occurs when a control developer wants a particular property to have a particular default value, and does not understand the dirty tracking mechanism, or doesn't care. For example, lets say the Text property a control is supposed to be some value that comes from a Session variable. Developer Joe writes the following code:
public class JoesControl : WebControl
{
public string Text
{
get { return this.ViewState["Text"] as string; }
set { this.ViewState["Text"] = value; }
}
protected override void OnLoad(EventArgs args)
{
if(!this.IsPostback)
{
this.Text = Session["SomeSessionKey"] as string;
}
base.OnLoad(e);
}
}
This developer has committed a ViewState crime, call the ViewState police! There's two big problems with this approach. First of all, since Joe is developing a control, and he has taken the time to create a public Text property, it stands to reason that Joe may want developers that use his control to be able to set the Text property to something else. Jane is a page developer that is attempting to do just that, like so:
<abc:JoesControl id="joe1" runat="server" Text="ViewState rocks!"/>
Jane is going to have a bad day. No matter what Jane puts into that Text attribute, Joe's control will refuse to listen to her. Poor Jane. She's using this control just like you use every other ASP.NET control, but this one works differently. Joe's control is overwriting Jane's Text value! Worse than that, since Joe sets it during the OnLoad phase, it is marked dirty in ViewState. So to add insult to injury, Jane is now incurring an increase in her page's serialized ViewState size for doing nothing more than putting Joe's Control on her page. I guess Joe doesn't like Jane very much. Maybe Joe's just trying to get back at Jane for something.
Well, since we all know which sex rules this world, we can assume Jane ends up getting Joe to fix his control. Much to Jane's delight, this is what Joe comes up with:
public class JoesControl : WebControl
{
public string Text
{
get
{
return this.ViewState["Text"] == null ?
Session["SomeSessionKey"] :
this.ViewState["Text"] as string;
}
set { this.ViewState["Text"] = value; }
}
}
Look at how much less code we have here. Joe doesn't even have to override OnLoad. Because the StateBag returns null if the given key does not exist, Joe can detect whether his Text property has been set already by checking for null. If it is, he can safely return his would-be default value. If it's not null, he happily returns whatever value it is. Simple as can be. Now when Jane uses Joe's control, not only is her Text attribute honored, she no longer incurs a hit on her ViewState size, either. Better behavior. Better performance. Less code. Everyone wins!
2. Persisting static data
By Static, I mean data that never changes or is not expected to change during the lifetime of a page, or even during the users session. Lets say Joe, our would-be shoddy asp.net developer, has been tasked with adding the current user's name to the top of a page in the company's eCommerce application. It's a nice way of telling the user, "hey, we know who you are!" It makes them feel special and that the site is working. Positive feedback. Lets say this eCommerce application has a business layer API that allows Joe to easily get the name of the currently authenticated user: CurrentUser.Name. Joe completes his task:
(ShoppingCart.aspx)
<asp:Label id="lblUserName" runat="server"/>
(ShoppingCart.aspx.cs)
protected override void OnLoad(EventArgs args)
{
this.lblUserName.Text = CurrentUser.Name;
base.OnLoad(e);
}
Sure enough, the current user's name will show up. Piece of cake, Joe thinks. Of course we know Joe has committed another sin. The label control he is using is tracking it's viewstate when it is assigned the current user's name. That means not only will the Label render the user name, but that user name will be encoded into the viewstate hidden form field. Why make ASP.NET go through all the work of serializing and deserializing the user name, when you are just going to reassign it after all? That's just rude! Even when confronted, Joe shrugs at the problem. It's only a few bytes! But it's a few bytes you can save so easily. There are two solutions to this problem. First... you could just disable viewstate on the label.
<asp:Label id="lblUserName" runat="server"
EnableViewState="false"/>
Problem solved. But there's an even better solution. Label has to be one of the most overused controls there are, bested only by the Panel control. It comes from the mindset of Visual Basic Programmers. To show text on a VB Form you needed a label. Labels are supposed to be the ASP.NET WebForm counterpart, so it's only nature to think you need a label to display a text value that isn't hardcoded in the webform. Fair enough, but that's not true. Label's render a <span> tag around their text content. You must ask yourself whether your really need this span tag at all. Unless you are applying a STYLE to this label, the answer is NO. This would suffice just fine:
<%= CurrentUser.Name %>
Not only do you get to avoid having to declare a label on the form (which means less code, albeit designer-generated code), but you've followed the spirit of the code-behind model: separation of code from design! In fact, if Joe's company had a dedicated designer responsible for the look and feel of the eCommerce site, Joe could have simply passed on the task. "That's the designers job", he could have balked, and rightfully so. There is another reason why you might THINK you need a Label, and that is when something in the code behind may need to programmatically access or manipulate that label. Ok, fair enough. But you must still ask yourself whether you really need a SPAN tag surrounding the text. Introducing the most underused controls in ASP.NET: The LITERAL!
<asp:Literal id="litUserName" runat="server"
EnableViewState="false"/>
No span tag here.
3. Persisting cheap data
This one is a superset of #2. Static data is definitely cheap to get. But not all cheap data is static. Sometimes we have data that may change during the lifetime of an application, possibly from moment to moment, but that data is virtually free to retrieve. By free, I mean the performance cost of looking it up is insignificant.
A common instance of this mistake is when populating a dropdown list of U.S. States. Unless you are writing a web application that you plan on warping back in time to December 7, 1787 (here), the list of US States is not going to change any time soon. However, as a programmer that hates to type, you certainly wouldn't want to have to type these states by hand into your web form. And in the event a state does rebel (we can only dream... you know who you are), you wouldn't want to have to perform a code change to strike it from the list.
Our proverbial programmer Joe decides he will populate his dropdown list from a USSTATES table in a database. The eCommerce site is already using a database, so its trivial for him to add the table and query it.
<asp:DropdownList id="lstStates" runat="server"
DataTextField="StateName" DataValueField="StateCode"/>
protected override void OnLoad(EventArgs args)
{
if(!this.IsPostback)
{
this.lstStates.DataSource = QueryDatabase();
this.lstStates.DataBind();
}
base.OnLoad(e);
}
As is the nature of databound controls in ASP.NET, the state dropdown will be using ViewState to remember it's databound list of list items. At the time of this ranting, there are a total of 50 US States. Not only does the dropdown list contain a ListItem for each and every state, but each and every one of those states and their state codes are being serialized into the encoded viewstate. That's a lot of data to be stuffing down the pipe every time the page loads, especially over a dialup connection. I often wonder what it would be like if I explained to my grandmother the reason why her internet is so slow is because her computer is telling the server what all the US States are. I don't think she'd understand. She'd probably just start explaining how when she was young, there were only 46 states. Too bad... those extra 4 states are really wearing down your bandwidth. Damn you late comers! You know who you are!
Like the problem with static data, the general solution to this problem is to just disable viewstate on the control. Unfortunately, that is not always going to work. Whether it does depends on the nature of the control you are binding, and what features of it you are dependant on. In this example, if Joe simply added EnableViewState="false" to the dropdown, and removed the if(!this.IsPostback) condition, he would successfully remove the state data from viewstate, but he would immediately run into a troubling problem. The dropdown will no longer restore it's selected item on postbacks. WAIT!!! This is another source of confusion with ViewState. The reason the dropdown fails to remember it's selected item on postbacks is NOT because you have disabled viewstate on it. Postback controls such as dropdownlist and textbox restore their posted state (the selected item of a dropdown ist 'posted') even when viewstate is disabled, because even with viewstate disabled the control is still able to post its value. It forgets it's selected value because you are rebinding it in OnLoad, which is after the dropdown has already loaded it's posted value. When you databind it again, the first thing it does is throw that into the bit bucket (you know, digital trash). That means if a user selects California from the list, then click on a submit button, the dropdown will stubbornly return the default item (the first item if you don't specify it otherwise). Thankfully, there is an easy solution: Move the DataBind into OnInit:
<asp:DropdownList id="lstStates" runat="server"
DataTextField="StateName" DataValueField="StateCode" EnableViewState="false"/>
protected override void OnInit(EventArgs args)
{
this.lstStates.DataSource = QueryDatabase();
this.lstStates.DataBind();
base.OnInit(e);
}
The short explanation for why this works: You are populating the dropdown list with items before it attempts to load it's posted value. Now the dropdown will behave just like it did when Joe first designed it, only the rather large list of states will NOT be persisted into the viewstate hidden field! Brilliant!
More importantly, this rule applies to any data that is cheap and easy to get to. You might argue that making a database query on every request is MORE costly than persisting the data through viewstate. In this case I believe you'd be wrong. Modern database systems (say, SQL Server) have sophisticated caching mechanism and are extremely efficient if configured correctly. The state list needs to be repopulated on every request no matter what you're doing. All you've done is change it from being pushed and pulled down a slow, unreliable 56kbps internet connection that may have to travel for thousands of miles, to being pulled over at worse a 10 megabit LAN connection a couple hundred feet between your internet server and database server. AND if you really wanted to improve things, you could cache the results of the database query in the application. You do the math!
4. Initializing child controls programmatically
Let's face it. You can't do everything declaratively. Sometimes you have to get logic involved. That's why we all have jobs, right? The trouble is ASP.NET does not provide an easy way to programmatically initialize properties of child controls correctly. You can override OnLoad and do it there -- but then you're persisting data that probably doesn't need to be persisted into viewstate. You can override OnInit and do it there instead, but that suffers from the same problem. Remember when we learned how ASP.NET calls TrackViewState() during the OnInit phase? It does this recursively on the entire control tree, but it does it from the BOTTOM of the tree UP! In other words, as a control or webform, the OnInit phase of your child controls occurs BEFORE your own. A control will begin tracking viewstate changes in this phase, which means by the time your own OnInit phase begins, your child controls viewstate are all already tracking!
Lets say Joe would like to display the current date and time in a label declared on the form.
<asp:Label id="lblDate" runat="server" />
protected override void OnInit(EventArgs args)
{
this.lblDate.Text = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss");
base.OnInit(e);
}
Even though Joe is setting the label text in the earliest event possible on his webform, it's already too late. The label is tracking viewstate changes, and the current date and time will inevitably be persisted into viewstate. This particular example could fall under the cheap data issue above. Joe could simply disable viewstate on the label to solve this problem. But here we are going to solve it a different way in order to illustrate an important concept. What would be nice is if Joe could declaratively set the label text to what he wants, something like:
<asp:Label id="lblDate" runat="server" Text="<%= DateTime.Now.ToString() %>" />
You may have intuitively attempted this before. But ASP.NET will slap you in the face for it. The "<%= %>" syntax can not be used to assign values to properties of server-side controls. Joe could use the "<%# %>" syntax instead, but that isn't very different than the databinding method we've already covered (disabling viewstate and databinding it every request).
The problem is we would like to be able to assign a value through code, but allow the control to continue to work in exactly the way it normally would. Perhaps some code is going to be manipulating this label, and we want any changes made to it to be persisted through ViewState like they normally would be. For example, maybe Joe wants to give the users a way to remove the date display from the form, replacing it with a blank date instead:
private void cmdRemoveDate_Click(object sender, EventArgs args)
{
this.lblDate.Text = "--/--/---- --:--:--";
}
If the user clicks this button, the current date and time will vanish. But if we solved our original viewstate problem by disabling viewstate on the label, the date and time will magically reappear again on the next postback that occurs, because the label's viewstate being disabled means it will not automatically be restored. That's not good. What on Earth is Joe supposed to do now?
What we really want is to declaratively set a value that is based on logic, not static. If it were declared the label could continue to work like it normally does -- the initial state wouldn't be persisted since it is set before viewstate is tracking, and changes do it would be persisted in viewstate. Like I said... ASP.NET does not provide an easy way to accomplish this task.
For you ASP.NET 2.0 developers out there, you do have the $ sign syntax, which allows you to use expression builders to declare values that actually come from a dynamic source (ie, resources, declared connection strings). But as far as I know, there's no expression builder for "just run this code" so I don't think that helps you either.
Also for ASP.NET 2.0 developers, there's OnPreInit. That is actually a great place to initialize child control properties programmatically because it occurs before the child control's OnInit (and therefore before it is tracking viewstate) and after the controls are created. However, OnPreInit is not recursive like the other control phase methods are. That means it is only accessible on the PAGE itself. That doesn't help you what-so-ever if you are developing a CONTROL. It's too bad OnPreInit isn't recursive just like OnInit, OnLoad, and OnPreRender are, I don't see a reason for the inconsistency.
The root of the problem is simply that we need to be able to assign the Text property of the label BEFORE it beings tracking its viewstate. We already know the page's OnInit event (the first event that occurs in the page) is already too late for that. So what if we could somehow hook into the Init event of the label? You can't add an event handler in code for that, because the soonest you can do it is in your OnInit which is after the source event has already occurred. And you can't do it in the constructor for the page, because declared controls are not yet created at that point. There are two possibilities:
1. Declaratively hook into the Init event:
<asp:Label id="lblDate" runat="server" OnInit="lblDate_Init" />
This works because the OnInit attribute is processed before the label's own OnInit event occurs, giving us an opportunity to manipulate it before it beings tracking viewstate changes. Our event handler would set its text.
2. Create a custom control:
public class DateTimeLabel : Label
{
public DateTimeLabel()
{
this.Text = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss");
}
}
Then instead of a regular label on the form, a DateTimeLabel is used. Since the control is initializing it's own state, it can do so before tracking begins. It does it during the constructor if possible, so that a declared value will be honored.
5. Initializing dynamically created controls programmatically
This is the same problem as before, but since you are in more control of the situation, it is much easier to solve. Lets say Joe has written a custom control that at some point is dynamically creating a Label.
public class JoesCustomControl : Control
{
protected override void CreateChildControls()
{
Label l = new Label();
this.Controls.Add(l);
l.Text = "Joe's label!";
}
}
Hmmm. When do dynamically created controls begin tracking viewstate? You can create and add dynamically created controls to your controls collection at almost any time during the page lifecycle, but ASP.NET uses the OnInit phase to start viewstate tracking. Won't our dynamic label miss out on that event?
No. The trick is, Controls.Add() isn't just a simple collection add request. It does much more. As soon as a dynamic control is added to the control collection of a control that is rooted in the page (if you follow its parent controls eventually you get to the page), ASP.NET plays "catch up" with the event sequence in that control and any controls it contains. So let's say you add a control dynamically in the OnPreRender event (although there plenty of reasons why you would not want to do that). At that point, your OnInit, LoadViewState, LoadPostBackData, and OnLoad events have transpired. The second the control enters your control collection, all of these events happen within the control.
That means my friend the dynamic control is tracking viewstate immediately after you add it. Besides your constructor, the earliest you can add dynamic controls is in OnInit, where child controls are already tracking viewstate. In Joe's control, he's adding them in the CreateChildControls() method, which ASP.NET calls whenever it needs to make sure child controls exist (when it is called can vary based on whether you are an INamingContainer, whether it is a postback, and whether anything else calls EnsureChildControl()). The latest this can happen is OnPreRender, but if it happens any time after or during OnInit, you will be dirtying viewstate again, Joe. The solution is simple but easy to miss:
public class JoesCustomControl : Control
{
protected override void CreateChildControls()
{
Label l = new Label();
l.Text = "Joe's label!";
this.Controls.Add(l);
}
}
Subtle. Instead of initializing the label's text after adding it to the control collection, Joe initializes it before it is added. This ensures without a doubt that the Label is not tracking viewstate when it is initialized.
Actually you can use this trick to do more than just initialize simple properties. You can databind controls even before they are part of the control tree. Remember our US State dropdown list example? If we can create that dropdown list dynamically, we can solve that problem without even disabling its viewstate:
public class JoesCustomControl : Control
{
protected override void OnInit(EventArgs args)
{
DropDownList states = new DropDownList();
states.DataSource = this.GetUSStatesFromDatabase();
states.DataBind();
this.Controls.Add(states);
}
}
It works amazingly well. The dropdown list will behave as if the states are simply built-in list items. They are not persisted in ViewState, yet ViewState is still enabled on the control, meaning you can still take advantage of its ViewState dependant features like the OnSelectedIndexChanged event. You can even do this with DataGrids, although that depends on how you are using it (you will run into problems if you are using sorting, paging, or using the SelectedIndex feature).
BE VIEWSTATE FRIENDLY
Now that you have a complete understanding of how viewstate does it's magic, and how it interacts with the page lifecycle in asp.net, it should be easy to be ViewState Friendly! That is the key really... ViewState optimization is easy as pie when you understand what is going on, often times resulting in even less code than the non-friendly code.
Have any suggestions, comments, error reports? Please leave a comment or send me an email!
60 Comments:
great article! thanks for clearing a lot of stuff out!
Thanks Joe. Where's Jane?
More of a question then comment:)
I'm going to turn view state off on a table:
asp:Table id="ErrorTable" EnableViewState="False"
runat="server" /asp:Table
Then, during the OnInit I'm going to dynamically add the rows with checkboxes in one TableCell and a single row of text from a text file in another cell:
override protected void OnInit(EventArgs e){
PopulateErrorTable();
InitializeComponent();
base.OnInit(e);
}
The checkboxes will be used to choose which rows a user wants to delete. Everything works great at this point. Now:
A user checks the boxes of the rows to delete and clicks the delete button which posts back. The table is re-built during OnInit, the posted values are then received, and the chosen rows are deleted from the text file. The page is then returned looking exactly like it did. The reason it looks the same is because the table was not re-built against the new text file. This leads to the problem....
If I re-build the table before returning, all future posted values are lost. Where do the posted values go? What is happening? What planet am I on?
This is a great comment! It illustrates how the Control Tree comes into play with postbacks. The problem you're having actually has nothing to do with ViewState though.
Here is your problem...
Remember that input controls post their values in HTML using a form of their UniqueID as the post key. Here's the scenario:
1. Page loads. Table has 3 rows in it. If you are not specifying an ID for each row, the rows have control IDs: _ctrl1, _ctrl2, _ctrl3
2. User checks off the 2nd row and clicks the delete submit button.
3. Server code deletes the file, then rebuilds the table. To rebuild, it clears the table's control collection and adds 2 new rows. Despite the original controls being cleared, the new rows get control IDs: _ctrl4, _ctrl5.
4. The table renders fine and everyone is happy thus far.
5. The user checks off the 2nd row because they want to delete it (they've checked _ctrl5), and they click the delete button.
6. In OnInit you rebuild the table once again, giving it the two rows again. Since this is a brand new request, these two rows get control IDs: _ctrl1, _ctrl2.
7. When the two checkboxes contained within those rows try to load their posted value, they find that there isn't one, because their IDs are different than they were the previous request.
The issue really stems from the fact that the autogenerated ID for a control does not start over just because you clear the control collection. For this to work, you must give an ID explicitely to the rows you create (you could make it row1, row2, etc). That way, when you rebuild the table in response to the delete button click, the new rows will have the same IDs that they will on the NEXT postback.
I hope that helps!!!
Wow!! You truly are an amazing developer with a great understanding of .NET. I can only hope to have your experience and abilities in the future. Thank you for the remarkable article which opened my eyes. Do you have any suggestions for a book that covers such topics? Whenever I search the books at borders, I only find beginner books. I am past beginner programming but, can’t find a good resource for more advanced topics, like your article here. Thanks again!!
Thank you so much :) Comments like this make me want to post more. As for a good book.... Developing ASP.NET Server Controls and Components by Microsoft Press is the best ASP.NET book I've found. But even then, nothing substitutes actual experience. Just dive in and try to understand the inner workings. Use Reflector on the framework if you have too :)
Thank you so much. I just spent several days fighting with dynamic controls and their state and this helped me immensely. This is not only great information, it's well-presented (and with humor, too!).
I'm pointing other folks at this as well.
Thanks!
First, finally a blog which allowes me to comment even if I'm not registered. Great!
Second, that article really helped me understanding the ViewState magic! It's added as a Favorite, and I think I will read it again (couldn't take it all in at once).
Great work!
Once again: Great article!
I still can't understand one thing: When dynamically adding controls to a page, we have to re-create them everytime the page postbacks. Imagine I have this huge control with LOTS of logic in the Page_Load event of the control. First, I press the Load Control-button. The control loads, the logic runs and the control path is added in a custom ViewState-entry. Then, the next postback, I press another button: Load Another Control. The page reloads, the old control is loaded via the ViewState control path and the huge block of logic in the Page_Load event runs again! The next second, the event handler of the Load Another Control-button runs, clears all controls and loads the new control into the placeholder (and sets the control path ViewState). What I mean is that we have alot of unnessecary work for the web server (one for every control change). There must be a better way, and I figured if you can't answer, no one can.
Good question.
The real question is, what expensive operations are you performing in the Page_Load of the first dynamicly loaded control and for what purpose? Is it retrieving large amounts of data? Is it making slow web requests to other servers?
If it's just data that we're talking about, you should cache that data on the server so you only need to retrieve it once. That would improve the performance significantly even without considering this dynamic control goodness.
Another possibility depends on how you are using that data. If all you are doing is displaying it to the user, and there's little to know interaction the user can have with that data (other than say clicking on links you generate, but not postbacks), then you can move all this expensive logic into the RENDER phase of that control. You see, SaveViewState() is called just AFTER the PreRender phase. So when the Render phase comes around you can now safely manipulate controls so that they render how you want them to but without worrying about bloating the size of their viewstate. So if you are rendering a datagrid, for example, and there's no postback actions they can perform to interact with that data, simply delay databinding the datagrid until Render time. No ViewState is used, and since it delayed until Render, the server wont do "unnecessary" work in the situation you described because the 1st control will be removed from the control collection before it gets a chance at life for that request.
If however you really need interaction with the data, this won't work because you aren't reproducing the controls that would be causing the postback events until its too late. So in that case you simply must find a way to make the process more efficient such as by caching the data on the server.
If the only thing you're really worried about is all the work the server must go through to load the dynamic control just to end up removing it, don't be... asp.net is extremely efficient at it. You think loading a dynamic control makes that control special? All the controls are dynamically added on every request, even the ones you declare on aspx form. The only real difference between a dynamic control and a static one is that static ones are "dynamically" added by the framework itself and "dynamic" controls are added by you.
Another trick, although I consider it's sort of a hack and don't recommend it, is to try and detect during the OnLoad phase whether the user has clicked a button that is going to cause the control to be swapped, and if so, don't do your expensive operation whatever that is. How you detect the pending event depends on what is causing it, and even then it can be affected by whether you have validators on the page (which is all part of the reason why I consider this technique a hack).
I hope that helps... if you post more specifics maybe I can give you some better ideas.
Yes, that sure helped.
I just thought I didn't handle those "dynamic" controls the correct way. I will look into making the Page_Load events more efficient.
Thanks again!
I will recommend this article to others as well.
Thank you!!
After being so confused with all the bits and peices and misinformation out there, I finally feel like I know what I'm doing! Keep up the good work.
It is 3:35am. I WAS going to sleep, just a (mis)fortune happened and I've found your article Dave. Sleep got me so I'm finished, ended at (after reading for more than hour!):
4. Initializing child controls programmatically...
I will read it to the end tomorrow (promise) with the comments, so I might repeat someone here.
BUT.
THIS IS THE BEST ASP.NET ARTICLE I HAVE EVER READ! And I've read many many asp.net stuff (a bad habbit I read more than I code :-), and even comparing it with articles/books with AHA! factor in them! When I was just starting to learn and understand ASP.NET (few years back) and this feeling was magical.
funny (are you missing an assembly reference?), intelligent, amazing and filled with great information (even 'empty' button will cause postback)... I am speechless, beacuse of 'teach' factor of article.
couple of points:
1. typo ;p ShoppingCart.aspx.cx
2. What about a Control State in ASP.NET 2.0, are you planing some article?
3. Persisting static data -> 3rd solution would be databinding in OnInit event (with viewstate enabled).
The thing which scared me the most was that you are pretty young. I thought you are som hardcore 40+ years old .NET veteran. Man you are cool!
Thanks cowgaR :) I fixed the typo.
An article on Control State would be fun. It isn't as mysterious and misunderstood as ViewState though :) Get some sleep.
Great article that covers everything about viewstate!!! Thank you very much.
Paketim
http://www.paketim.com
Hi,
Testing some of the concepts you raised, i'm not sure if I fully understand.
If I build a simple page with a label and button:
[asp:Label ID="Label1" runat="server" Text="design"][/asp:Label]
[asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" /]
The Label loads design time properties and does not alter the ViewState. On inspection the page's viewstate is 52 chars.
The following code is added for the OnClick event for the button to dynamcially alter the Label Text:
protected void Button1_Click(object sender, EventArgs e)
{
Label1.Text = "button";
}
On postback the ViewState is now 92 chars. Ok, all make sense so far.
If I test the 'Persisting cheap data' theory by over riding OnInit it gets confusting. The following code is added:
protected override void OnInit(EventArgs e)
{
this.Label1.Text = "oninit";
base.OnInit(e);
}
On loading the ViewState is 92 chars, suggesting that the property change is persisted into the ViewState.
Have I misunderstood the point?
OnInit occurs from the BOTTOM of the control tree, up. That means when you override OnInit in a page, all of the controls on it have already had their OnInit fire and so are therefore tracking viewstate changes already. In the persisting cheap data issue, a dropdown is databound during OnInit, and the viewstate on it is disabled.
On the other hand, if you just wanted to initialize a property of a custom control, you can do that in _it's_ OnInit. Because like I said, in OnInit, "your" viewstate isn't tracking, but your child controls viewstate are. You can test that theory by creating a MyLabel that inherits from label, and putting your Text="oninit" into the MyLabel.OnInit method. You could also put a breakpoint in both places, and you'll see that it fires in the label first.
Thanks for the comment :)
Thanks for that, I now get the point about bottom-up OnInit processing.
However, using custom controls is not very satisfactory as multiple controls will require multiple OnInit which could become messy, especially when they are sharing business logic.
Reading back through your very comprehensive post I noted your comment on OnPreInit, and simply renaming the procedure in my example above, produced a ViewState of 52 chars. Prefect I thought, problem solved.
However, transfering this simple test to a more complex example with master pages is producing unusual results. Steping through the OnPreInit for the nested content page, controls appear in intelisence as null and I am unable to set a property.
Is the control life cycle different for content pages and why do you think content pages are behaving differently?
Agreed that creating a custom control is not the way to go, but it illustrates there is a need in the event model for a way to initialize child controls correctly. My article on the CodeExpressionBuilder presents another solution that you should read.
As for OnPreInit... that will work perfectly if you do it in the page. But OnPreInit does not exist in controls, so you cannot use this solution to initialize child controls _from_ a custom control. Its a bummer.
Your problem with having the controls show up as null...
When a page is attached to a master page, its control variables are not assigned until after OnPreInit has done its job. When there isn't a master page, then its already done. I haven't delved into the details yet to explain why it works this way, it just does. But the happy news is you just need to call base.OnPreInit first, then your controls should pop into existence.
Thanks for that, however I think there might be a deaper problem. Consider the following Content page:
<%@ Page Language="C#" MasterPageFile="~/application.master" AutoEventWireup="true" CodeFile="test.aspx.cs" Inherits="test" Title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="content" Runat="Server">
<asp:Label ID="Label1" runat="server" Text="design"></asp:Label>
</asp:Content>
Override OnPreInit and as suggested, call base.OnPreInit before attempting the dynamically change the property:
public partial class test : System.Web.UI.Page
{
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
this.Label1.Text = "oninit";
}
}
This will throw the error "Object reference not set to an instance of an object".
I will have to do some more research.
Perhaps OnPreInit isn't useful then. It was only introduced to enable programmatic access to the page's master page, so you could assign it dynamically for example.
Thanks for your researching efforts. I will look more deeply into these examples as soon as I get a chance.
Question -- you are getting a null reference exception even after calling base.OnPreInit... but is the label variable null? I'm curious whether you are getting a different result than me (the control is not null for me). It could be the null ref is coming from deeper within the label control, perhaps when trying to access its page context or something which may not be setup yet. I'll find out, if you dont first :)
WOW!!! I just want to encourage you to make more articles. Your writing is so great and you clarify me a lot on VIEWSTATE. Did you need a job in Montreal Canada, this is a really great city!!
Thank you..!
You said:
Not specifying an ID has advantages, but that is a different subject
I didn't know that there were advantages outside of me not having to type. Can you post some links or provide some examples? I am very interested.
Well, depending on the element type, not specifing an ID means there won't be an ID attribute on it in html, and that can result in a noticable decrease in the rendered size of your page. Imaging a datagrid with id="grid1", with 100 rows. In one of the columns you have a label with id="label1". Well the DataGrid implements INamingContainer, and each of the datagrid's items do too. So each label's fully qualified id will be grid1_ctrlX_label1 (where X increments for each row). That ID is about 20 bytes, and there are 100 of them on the page, soooo that harmless looking id="label1" increased your rendering size by at least 2,000 bytes.
Another advantage has to do again with INamingContainer. You're supposed to implement that interface if you're a composite control, because presumably you will be providing an ID to your child controls. If someone puts two of your controls on the same page, for example, there'd be two of each control, thus duplicating the same IDs. INamingContainer prevents that by assigning scope to the child control IDs, making them unique once again. BUT... if you don't assign an ID to your child controls, you don't have to implement INamingContainer. Each of the duplicated controls in this example will be assigned an auto-generated ID that is unique. And that has further benefits down the line, because since your composite control doesn't implement INamingContainer, if any of your child controls render their client ID attribute (which they can choose to do so whether or not you assign them an ID), it will be that much shorter in length.
But it seems to be the defacto standard that you should always assign an ID. There are even some third party controls I've run into that throw an exception if you don't. It's just not true. An ID is only really necessary the control is declared and you need to refer to the control in the code-behind, if you have to specify an ID to get it to render a client ID (because you need to reference it in javascript), or if you need to easily point another control at it (like validators). Perhaps some other reasons too, but thats the gist of it.
Its all penny pinching really, but when consider a developer who cares about things like this and compare his pages against a developer who doesn't care, his will definitely be smaller and load faster.
Thanks for the comment and attention to detail :)
Excellent article thanks.
It would be useful if you could extend the article to clarify how viewstate is associated with event handlers e.g. the dropdownlist control's SelectedIndexChanged event handler. My handler doesn't get called when created in createchildcontrols!
Thanks for the article. I have an application that creates a dynamic dropdownlist within a table on my page. I had a problem with not being able to change the value of the dropdown on successive postbacks (the viewstate was overriding my changes). What I am wondering is if there is any time the LoadViewState won't be immediately called when I add my dropdown to the tablecell (which is added to the tablerow -> table -> form -> page). The page behavior seems to be finishing all of my page_load function before calling the LoadViewState for my dropdown.
Because I don't feel like posting a huge amount, a much more complete explanation of my problem and my workaround can be found at
http://forums.asp.net/1333193/ShowThread.aspx#1333193
Posted 7/5/2006
Think about a statically declared dropdown list. It isn't really static. Its still a dynamically created control, its just that ASP.NET creates it instead of you, thats the only difference. If you wanted to declare a dropdown where by default the selected value was "abc" you would declare it something like this:
<asp:DropDownList runat="server" SelectedValue="abc" />
The SelectedValue attribute is translated into code where that property is set before the control is added to the control tree. This occurs each and every request including postbacks. However, if we just stopped there, the dropdown wouldnt behave correctly -- if the user changes the selected item its supposed to remember that, just like textboxes remember the data in them. So the dropdown looks at the post data collection in the request, it looks there for a key that corresponds to its unique ID. If it finds it, the value contains what the selected item was on the previous request, so it resets the selected value to that value.
So if you think about it this way, you can see that the dropdown is not behaving "buggy" like you think it is. It's doing exactly what it is supposed to do.
If you really mean to reset the selected value on a postback, thus totally ignoring whatever the user actually selected, then you'll have to set the selected value AFTER it has been added to the control tree. You could do that by splitting your BuildTable logic into two pieces -- BuildTable and SetSelectedValues, or what have you.
But the real problem I see here is that you are not taking advantage of DataBinding. You are building a table quite manually rather than using a DataList, GridView, or DataGrid, or even a Repeater control. If you used one of those instead, not only would your code be simpler (you wouldn't have to manually recreate the controls), but you wouldn't have run into this problem to begin with. You'd simply call DataBind over again, and the existing UI (and its 'incorrect selected value') would be tossed aside and rebuilt automatically.
I'll cross post this in the forum too. I hope I addressed your problem, I tried to understand it the best I could. Let me know. Thanks :)
Nice article. What do you mean by the following. Can you expand?
...that is just one reason why ASP.NET always tends to use String.Empty ("") instead of null.
DropDown guy here,
"then you'll have to set the selected value AFTER it has been added to the control tree"
I tried adding the dropdown to my tablecell at different locations (before repopulating & setting the value among other places) but the behavior was the same no matter the location.
I am pretty sure that I had a complete "control tree" from the page to the dropdown when I added it to my tablecell, yet no matter the location of setting the SelectedValue the view state would override it. All of this evidence is suggesting that the view state isn't loading for my dropdown until after the Page_Load procedure returns. Can this be true?
Thanks for the fast reply yesterday,
Robert
ViewState loads just before Load. But for a control that is added dynamically after the fact, it is loaded as soon as the control becomes rooted in the control tree.
In short, I think ViewState has nothing to do with your problem. The dropdown is simply loading its posted value. I thought you adding it to the tree first, then setting the selected value, would solve that problem, but I forgot one possibility. Posted data is actually loaded in two phases -- one before Load and one after Load. The one after load is so that controls added dynamically during the Load event can still retrieve their posted values, depsite the fact that the framework already processed the posted data. So perhaps it is this second postback data load phase, which occurs after load, and therefore after you create the dropdown and set the value, which is causing your unwanted behavior.
A solution would be to create the controls just like you already do, but in the OnInit method instead of the OnLoad method. That will allow the dropdown to load its posted value in its natural way, the first postback data load attempt that occurs before Load. Then in your Load event you can override the selected value(s), knowing with confidence that there's nothing left in the event sequence that may thwart your efforts :)
By the way I still say that you aught to be using databinding at least with a repeater here. You're doing things so manually this way. If you've never used a repeater or other databound control then you may think I'm crazy because they may seem too complex and overkill for what you need.. but once you understand how to use them, it makes sense, and its natural, and best of all, you avoid problems like the one you're having.
> Null vs. String.Empty
What I mean is that ASP.NET controls tend to use String.Empty as default values instead of null. The concept of null is blurry at best in the web environment. For example, you render a textbox that is blank by default. A postback comes around, and the value is still blank. How should you represent that value -- null, or string.Empty? If you say null, would there ever be a case where you would use string.Empty? The choice is ambiguous. ASP.NET chooses String.Empty.
Fantastic article: found exactly what I was looking for - when recreating a dynamic gridview the viewstate was not loaded (seemingly) although the number of rows was correct... turns out I was binding the grid *before* adding the control, so the data was not dirty, thus not persisted in viewstate.
Thanks.
Excellent article (certainly a master piece about ASP.Net).
One point is still unclear for me: what should happen for the ViewState when the control tree structure evolve? (this siuation occurs often when controls are dynamically generated)
Joannès
Great article. Just out of curiosity, I noticed for all of your page event handlers...rather than creating an event handler like:
protected void Page_Load(object sender, Eventargs e)
You override the OnEvent method of the base class:
protected override void OnLoad(EventArgs args)
Is there a specific reason for this, a design practice, a performance issue, or does it not matter?
Drop Down guy,
Wow I did not know that the DataGrid component had so much functionality. After looking into it I see that you can even edit the information right within the table. Before I thought that it wasn't able to edit so I easily accepted "hardcoding" the entire table. I will be using this from now on.
Thanks
I've been away a while, sorry! I'll try to catch up with some of these questions. First up:
>> what should happen for the ViewState when the control tree structure evolve? <<
It's ok for the control tree to change on postbacks, as long as the state of the tree at the time viewstate is loaded is compatible with the saved viewstate from the previous request. Once it is loaded you can make all the changes you want. The final control tree at SaveViewState time is what determines what the next request's control tree should be like. Now by "compatible" I mean that there shouldn't be conflicts or 'shifted' controls. If there's a control with id "_ctrl1" it better be the same ID it had when it's viewstate was saved. A conflict like that can occur if you are producing dynamic controls in varying numbers on postbacks and you don't assign them IDs that are the same each time.
next up:
>> You override the OnEvent method of the base class <<
It's just a preference really. It feels more object oriented to me I guess :) Technically I'd say its safer to use the event, because with override you do have the responsibility to call base. If you don't, the event handlers aren't called. It also has to do with the fact that I don't use the designer. I prefer to code the markup by hand. I switch to code view and just 'override' the event I need, which is easier than manually hooking into the event.
>> Wow I did not know that the DataGrid component had so much functionality <<
If you're using asp.net 2.0, you should see the GridView :)
>> turns out I was binding the grid *before* adding the control, so the data was not dirty, thus not persisted in viewstate <<
Thats a trick I have often used on purpose, to get a datagrid full of data but without it being saved to viewstate. In 2.0 there are better ways to accomplish this though, without using a 'trick'. Using a declarative datasource control and disabling viewstate prompts it to reobtain the data every request automatically.
Great article!
But... I have a question:
you said that a created dynamically control where I want to put a specified text on loading needs no viewstate, isn't it?
But... is there a case when a created dynamically control NEEDS EnableViewState=true? I've always used it... what a trouble!!!!!
Thanks :) It's not true that dynamically created controls dont need viewstate. Whether you need it or not depends on what you are planning to do with the control. If you are going to be changing its text in response to some event (say they click on a button), and you want the change to be 'remembered' across subsequent postbacks, then you need viewstate, even if its dynamic.
infinity, THANKS for sharing ur so deep knowledge in ViewState
I currently have this problem - wanna add a second header to 1.1 datagrid, this is my Page_Load:
private void Page_Load(object sender, EventArgs e)
{
if (! IsPostBack)
{
DataGrid1.DataSource = CreateDataSource();
DataGrid1.DataBind();
}
DataGridItem head = new DataGridItem(0, 0, ListItemType.Header);
DataGrid1.Controls[0].Controls.AddAt(0, head);
int intCount;
TableCell fcell;
for(intCount = 0; intCount < 4; intCount++)
{
fcell = new TableCell();
fcell.Text = "qqqqqqqqq";
head.Cells.Add(fcell);
}
}
it behaves quite strange - on each postback the last row disappears
can u plz give me an advice
TIA
TIA --
The DataGrid internally remembers the number of rows there are. Since you are forcibly adding your own row, that mechanism is being thrown off. That's my guess.
An easy solution for adding a "second header" isn't coming to mind. I once created a custom datagrid that inherited from datagird that had the ability to add extra spanning footer rows, but it wasn't straight forward and I don't remember how it worked. I wish I could help more, I'll comment again if I come up with something.
OK, I did something like the example given above:
asp:DropdownList id="lstStates" runat="server"
DataTextField="StateName" DataValueField="StateCode" EnableViewState="false"
protected override void OnInit(EventArgs args)
{
this.lstStates.DataSource = QueryDatabase();
this.lstStates.DataBind();
base.OnInit(e);
}
and I also have something like this to pick the correct selectedItem in the drop down
private void Page_Load(object sender, EventArgs e)
{
if(!IsPostBack)
{
personalDetails = DataBase.GetPersonalDetails(Id);
DataBind();
}
}
protected override void OnDataBinding(EventArgs e)
{
if(ddlState.Items.FindByValue(personalDetails.Data.State)!= null)
ddlState.SelectedValue = personalDetails.Data.State;
}
The dropdownlist gets populated correctly, and in OnDataBinding() the selectedvalue gets set, but by the time I get to OnPreRender, selectedindex is always returned to 0. Can anyone see what I am doing wrong?
Jamie -- it looks alright to me. Just to be sure I setup a page that works just like this and had no issues. Perhaps there is code elsewhere on the page manipulating the dropdown? Is there code somewhere else that may be calling DataBind()? When DataBind is called, the dropdown resets, so that would do it. Also, are you certain the selected value is set? You said it is, but have you actually hit a breakpoint there? What is the selected index immediately after it is set (in OnDatabinding?)
When I exit Page.OnDataBinding()
the selectedValue is correct (verified in debugger).I even added the following in OnInit
ddlState.DataBinding +=new EventHandler(ddlState_DataBinding);
so I could see if the ddl is getting bound again. The event fires again straight after I call Page.DataBind(). But even when exiting the event handler for a second time, the selectedValue is still correct. Then I can't trace it until I hook the OnPrerender event. At which point it has been reset.
Well you are getting the 2nd event, because DataBind() it recursive. You are calling databind on the page, which recursively databinds all the controls on the page. But in your case that isn't causing the problem, because you only databind it the 2nd time on !IsPostback, and you set the selected index after that anyway.
Do you mind sending me the page? Obviously I don't have your middle tier stuff, but I can stub out what doesn't work to try and reproduce your issue. My email is in my profile.
Congratulations Dave, this is a great article.
Well, I realised that I have to set the selected value after ondatabinding() rather than within it. It's a shame there's no way to bind the selected value at the same time. Thanks for the help Dave, and a very valuable article.
Excellent stuff! Much like Joe I've been struggling with ViewState during many a night trying to develop a custom datagrid-like control.
Maybe you could expand your post with how al of this leads up to the pattern whereby in custom controls Joe needs to rebuild the control tree before ViewState is loaded, if you know what I mean... That part cost me hours and hours of sleep!
PS: luckily for some, not all interviewers are as knowledgeable on the control lifecycle!
Bart,
Thanks for reading :) I'm working on a new article about dynamic controls that I think may address the issue you are referring to.
I apologize for what will be a dumb question, but I am just learning this. Based upon a selection of a node on a treeview, I dynamically add imagebutton controls to a placeholder. I use AddHandler to attach an onclick event handler to each imagebutton. The buttons get rendered properly, and when I click on one of the images, a postback occurs, but my event handler is never fired.
Through experimentation, it appears that I can successfully add the images and the event handler during the page_load, but if I add them in the treeview.selectednodechanged event routine, it does not.
Thanks for the great post. Could you help me understand the sequence of events here as it relates to connecting the event handlers?
Ed
Ed --
Maybe you can send me some code. Make sure you are adding the dynamic controls on every request. If you are only adding them in response to an event, since that event doesn't fire every postback, they won't be added every postback. I'm planning to put together an article all about dynamic controls. No idea when I'll finish it though :)
I do not need these controls on further postbacks. What I am doing is, based upon the selection of a node in a treeview (essentially, selecting a directory on the server containing photos), I create in a placeholder a bunch of imagebuttons (thumbnails of the photos in the directory). I attach a click event handler to each of the thumbnail images (when thumbnail is clicked, I dynamically create just a single image control). I am creating these thumbnail controls in the treeview.selectednodechanged event. The images display fine, but the click eventhandler never gets fired. Through experimenting, I have found that if I create the imagebuttons any later than the page_load event, the controls appear but their event handler does not fire.
How do I create controls with wired event handlers in a control event?
I am looking at Atlas right now, but am just curious about understanding the page cycle better -- particularly as it relates to dynamic controls and event handlers.
By the way, I have figured out a hokey way to created the thumbnail imagebuttons in the page_load by just checking if a treenode has been selected (therefore I do not need to use the treeview.selectednodechanged event). After processing it, I unselect it.
tenholde --
You _do_ need the controls on subsequent postbacks. When you dynamically add a control, it is not going to exist on the next postback unless you add it again. ViewState does not maintain what controls are on the page, it only maintains the state of the controls that are already on the page. It sounds like your event handler doesn't fire for the ImageButtons because they simply don't exist after they click on one.
It also sounds like this is a case of using dynamic controls when there's a much, much simplier way. Why don't you use a DataList or a Repeater? You'd build up a collection of the images in the desired directory, assign the collection to the datasource, then call DataBind() on the DataList or Repeater. Done! No dynamic controls needed at all. That's better not only because it is easier, but because it is in the spirit of the "ASP.NET way" -- doing it with dynamic controls is like trying to do things the "ASP" or "PHP" way but in ASP.NET. It seperates UI from Logic. It allows you to "declare" what the ImageButtons will look like, even put HTML structure around them, without writing any code. The only code that must exist is determining what the collection of images is. Its much more maintainable too! It seems that people use dynamic controls more often than they need to, it and causes them to run into complex problems like the on you are having. That's why I'm planning on putting together an article all about dynamic controls.
I know I'm just giving you high level advice, it may not help you in your immediate situation. But I __strongly__ recommend researching the DataList or Repeater and seeing about doing it that way. It would be so much simplier.
Okay,
I followed your advice and used a repeater control, and everything is falling into place. Thank you for your help.
Ed
Great Article, it has filled in a lot of empty spots. I have a situation that I still don't quit understand...
I use a pop-up to gather some information and have registered a PostBackEvent handler (IPostBackEventHandler) on the initial page. When the postback handler is called I try to update state... the problem is it appears to recycle through the page processing through the following:
Page_Init(object sender, EventArgs e)
Page_Load(object sender, EventArgs e)
RaisePostBackEvent(string eventArgument)
Page_PreRender(object sender, EventArgs e)
Page_PreRenderComplete(object sender, EventArgs e) ***Value correct here*** checked ViewState[key]
Page_Init(object sender, EventArgs e)
Page_Load(object sender, EventArgs e) ***Value INCORRECT*** ViewState[key]
Page_PreRender(object sender, EventArgs e)
Page_PreRenderComplete(object sender, EventArgs e)
Anon -- If you could, please move your comment over to my new blog (link at the top of the article). I don't quite understand your situation. You have a page that is opening a popup window? Which page is posting back and how?
Awesome article!!! I alwayz prefer to read article only with maximum code not other words....But this article hold me to read its words before the code inside...wonderfull article...keep posting...
Mangesh Hendre(mangeshhendre@gmail.com)
Wow, thank you. This is an amazing article, i was wondering why my clients thought the website was "slow", not any more though :).
Thanks again, I am going to make sure all my colleagues read this article, then test them on it :)
All the best
Amar
Post a Comment
<< Home