From the translator: This is a translation of an article from one of the ASP.NET developers, which details the page state control mechanism, ViewState. Despite the fact that the article was written in 2006, it still has not lost its relevance.
ViewState is a very incomprehensible creature. I will try to put an end to all misinterpretations, and I will try to explain how the ViewState mechanism actually works, from beginning to end, looking at it from different points of view.
There are many articles whose authors are trying to dispel the myths about ViewState. You can even think that this is all - the fight against windmills (where ViewState is windmills, and the Internet is a tool of struggle). But I will report to you, the mills have not stopped yet. On the contrary, they spin around and fill up your living room. It’s time to strike another blow at them. Do not worry, while writing this article, no windmill was damaged.
Not that there are no good sources of information about ViewState around, they just all miss something, and thus only contribute to the general confusion around ViewState. For example, one of the key points is understanding how ViewState tracking works. And
here is a very good and deep article about ViewState, which does not even concern this! Or here's an article on
W3Schools that suggests that the form settings sent to the server are stored in ViewState, which is not true. (Do not believe it? Turn off the textbox's ViewState using their example and run it again). And here
is the MSDN documentation that describes how controls keep their state between postbacks. This is not to say that this documentation is incorrect, but it contains a statement that is not completely correct:
')
“If the control uses ViewState for a property instead of a hidden field, this property will be automatically saved between sending responses to the client.”
It seems that here it is meant that everything that is shoved in ViewState will be sent to the client. NOT TRUE! It is clear where such confusion comes from around the ViewState. Nowhere on the Internet I could not find a 100% complete and accurate description of his work! The best article I have seen is
Scott Mitchell's . It is required to read. However, it does not explain the interaction of parent and child controls during the initialization and tracking of the ViewState, and this already causes a lot of misunderstanding of the ViewState, at least based on my experience.
So the first goal of this article is to give an exhaustive explanation of how ViewState functions, from beginning to end, possibly closing the white spots left by other articles. After a detailed description of the ViewState process, I will show several errors that developers make when using ViewState, as a rule, without even knowing it, and how to correct these errors. It should be noted that I wrote this article based on ASP.NET 1.x. However, there are very few changes in the ViewState mechanism in ASP.NET 2.0. In particular, a new type of ViewState, ControlState, has appeared, but it is used in the same way as ViewState, so we can simply ignore it.
First let me explain why I believe that understanding the essence of ViewState is very important:
A misunderstanding of ViewState leads to ...
- Loss of important information
- ViewState attacks
- Poor performance - down to NO PRODUCTIVITY
- Poor extensibility - how many users can you serve if each one of them sends 50k with each request?
- Bad design in general
- Headache, nausea, dizziness and irreversible curvature of the shape of the eyebrows.
And now let's start from the beginning.
What does viewstate do?
This is a list of the main functions of ViewState. Each of them serves a specific purpose. Next we look at how they achieve these goals.
- Saves control data by key, like a hash table
- Tracks state changes of ViewState
- Serializes and deserializes stored data into a hidden field on the client.
- Automatically restores postback data
It is even more important to understand that ViewState does NOT.
What does not viewstate do?
- Automatically saves the state of class fields (hidden, protected, or open)
- Memorizes any information when loading page (only postback'i)
- Eliminates the need to load data with every request
- Responsible for loading data that was sent to the server, for example, entered in a text field (although ViewState plays an important role here)
- Brews you coffee
Despite the fact that ViewState has one main goal in the ASP.NET Framework, the four roles that it plays in the page life cycle are somewhat different from each other. It would be logical to separate them and consider them one by one. Often it is a pile of information about ViewState that confuses people. I hope that now we have broken down the ViewState into more digestible parts.
1. ViewState saves data
If you have ever used hash tables, you already know everything. Nothing extraordinary, ViewState has an indexer that accepts a string as a key and any object as a value. For example:
ViewState[ "Key1" ] = 123.45M; // decimal
ViewState[ "Key2" ] = "abc" ; // string
ViewState[ "Key3" ] = DateTime .Now; // DateTime
* This source code was highlighted with Source Code Highlighter .
In fact, here “ViewState” is just a name. ViewState is a protected property defined by the System.Web.UI.Control class, from which all controls, including server controls, user controls, and pages are inherited. The type of this property is
System.Web.UI.StateBag . Strictly speaking, the StateBag class has nothing to do with ASP.NET. It is defined in the System.Web assembly, but apart from being dependent on the State Formatter defined in System.Web.UI, there are no prerequisites for the StateBag to be defined in the same name as ArrayList in the System namespace. Collections. In practice, server controls use ViewState to store most, if not all, of their properties. This is true for almost all ready-made controls from Microsoft (such as Label, TextBox, Button).
It is important! You should know such things about the controls you use. Read this sentence again. Seriously ... and the third time:
Server controls use ViewState to store most, if not all, of their properties . Based on your experience, thinking about the properties you probably imagine something like:
public string Text
{
get { return _text; }
set { _text = value ; }
}
* This source code was highlighted with Source Code Highlighter .
It is important to know that most of the properties in ASP.NET controls look different. Instead, they use ViewState rather than a hidden variable:
public string Text
{
get { return ( string )ViewState[ "Text" ]; }
set { ViewState[ "Text" ] = value ; }
}
* This source code was highlighted with Source Code Highlighter .
And once again I accentuate your attention - this is true for almost ALL PROPERTIES, even Style (generally speaking Style does this by implementing the IStateManager, but the essence is the same). When you write your controls, you should usually stick to this template, but first you should think about what can and cannot be dynamically changed on postbacks. But this is a slightly different topic for conversation. It is also important to understand how the
default values are implemented within this technique. When you think about the usual properties that have default values, you probably imagine something like:
public class MyClass
{
private string _text = "Default Value!" ;
public string Text
{
get { return _text; }
set { _text = value ; }
}
}
* This source code was highlighted with Source Code Highlighter .
The default value is such, since it is the property that returns it, if it has never been assigned anything. How can we achieve the same behavior, but using ViewState? That's how:
public string Text
{
get
{
return ViewState[ "Text" ] == null ?
"Default Value!" :
( string )ViewState[ "Text" ];
}
set { ViewState[ "Text" ] = value ; }
}
* This source code was highlighted with Source Code Highlighter .
Like a hash table, StateBag returns null by key if it does not contain an entry with that key. That is, if the value of the entry is null, then it was not created and the default value must be returned, otherwise we return the value of the entry. The most attentive probably noticed the difference between these two implementations. In the case of ViewState, setting the property to null will send the property back to its default state. And if you assign a normal property to null, then it will simply contain null. This is one of the reasons why ASP.NET constantly uses String.Empty (""), and not null. This is also not very important for ready-made controls, since those of their properties that can be null, and so have a default value of null. All I can say is keep this in mind when writing your controls. Finally, although the ViewState is used to store property values, it is not limited to this. In the control or on the page, you can always use ViewState at any time for any purpose, and not just as a container of properties. Sometimes it is useful to memorize in this way some information, but this, again, is a topic for another conversation.
2. ViewState tracks changes
Has it ever happened to you that you assigned a value to a property of a control and then you felt as if it were ... dirty? It definitely happened to me. Moreover, after twenty hours of setting the properties in the office, I became so dirty that my wife refused to kiss me, unless I brought flowers to disguise the smell. Honestly! Okay, okay, setting properties doesn't make you dirty. But she makes a dirty statebag! A StateBag is not just a dumb collection of keys and values, like a hash table (just don’t tell the hash table that I called it, it’s scary in anger). In addition to storing values by key, StateBag is able to TRACK changes (hereinafter “tracking” - approx. Lane). Tracking is either on or off. It can be enabled by calling the TrackViewState () method, but if you turn it on, you cannot turn it off. WHEN AND ONLY WHEN TRACKING is ON, any change to any StateBag record will mark this entry as “Dirty” (hereinafter “dirty” - approx. Lane). StateBag even has a special method that is needed for checking, and the entry is dirty - IsItemDirty (string key). You can also manually tag the entry by calling the SetItemDirty (string key) method. To illustrate this, let's assume that we have an untracked StateBag:
stateBag.IsItemDirty( "key" ); // false
stateBag[ "key" ] = "abc" ;
stateBag.IsItemDirty( "key" ); // false
stateBag[ "key" ] = "def" ;
stateBag.IsItemDirty( "key" ); // false
stateBag.TrackViewState();
stateBag.IsItemDirty( "key" ); // , false
stateBag[ "key" ] = "ghi" ;
stateBag.IsItemDirty( "key" ); // TRUE!
stateBag.SetItemDirty( "key" , false );
stateBag.IsItemDirty( "key" ); // FALSE!
* This source code was highlighted with Source Code Highlighter .
Generally speaking, tracking allows StateBag to
track which records changed after TrackViewState () was called. Those values that were assigned before calling this method will not be tracked. It is important to understand that any assignment operation will mark a record as dirty, even if the value assigned is the same as the current one!
stateBag[ "key" ] = "abc" ;
stateBag.IsItemDirty( "key" ); // false
stateBag.TrackViewState();
stateBag[ "key" ] = "abc" ;
stateBag.IsItemDirty( "key" ); // true
* This source code was highlighted with Source Code Highlighter .
One could write the ViewState so that it checks the new and old values before determining how to mark the record. But remember that ViewState allows you to use any objects as values, which means that this is not a banal string comparison, and not every object implements IComparable. Alas, since serialization and deserialization take place, the object that you put into ViewState will not be the same object after postback. Such comparisons are not needed by ViewState, which is why it does not do them. This is what tracking is all about.
But you probably wonder why the StateBag needs this function. Why would anyone suddenly need to know if there were any changes after TrackViewState () was called? Why not just use the usual collection and not know the problems? And this is one of the places that cause all the confusion around ViewState. I conducted interviews with many ASP.NET specialists who have years of use of this technology in their resumes, and they could not prove they understand this issue. Moreover, I have never interviewed a person who understood this! First of all, in order to understand why tracking is needed, you need to understand a little deeper how ASP.NET works with declarative controls. Declarative - these are the controls that are defined in ASPX or ASCX files:
< asp:Label id ="lbl1" runat ="server" Text ="Hello World" />
* This source code was highlighted with Source Code Highlighter .
Here is the label that is defined in your file. The next thing to understand is the ability of ASP.NET to associate attributes with properties of a control. When ASP.NET parses the file, and detects a tag with runat = server, it creates an instance of the declared control. It names the corresponding variable according to the value you specified for the ID attribute (by the way, not everyone knows that it is not necessary to set the ID, ASP.NET will generate it itself. This may be useful, but this is not the case now). But that's not all. A control tag can have a bunch of attributes. In our Label example, we have the Text attribute, and its value is “Hello World.” Using reflection, ASP.NET can determine if the control has a corresponding property and
assign it a declared value. Of course, the attribute value is declared as a string (after all, it is declared in a text markup file), so if the corresponding property is of type not string, you need to determine how to cast the string to the desired type before calling the setter. How this happens is a separate topic (here TypeConverters and Parse static methods are used). It is enough that the conversion somehow occurs, and the property setter is called.
Remember that important statement from the first paragraph? Here it is again: Server controls use ViewState to store most, if not all, of their properties. This means that when you define an attribute of a server control, this value is usually saved as an entry in the ViewState. Now remember how tracking works. Remember that if the StateBag tracks changes, assigning values to records will mark them as dirty. And if tracking is disabled, no records will be marked. The question arises - when ASP.NET calls the property setter that matches the declared attribute, does the StateBag track changes? The answer is no, it doesn’t, since tracking doesn’t start until someone calls TrackViewState (), and ASP.NET does it in the page initialization phase. This technique allows you to easily distinguish the values specified declaratively from the dynamically specified values. If you don’t understand how important this is, please continue reading.
3. Silverization and deserialization
Apart from the ASP.NET creation process of declarative controls, the first two ViewState functions examined were directly related to the StateBag class (similar to a hash table, and tracking dirty records). The time has come for really serious things. Now we will talk about how ASP.NET uses these properties of the StateBag to implement the (black) magic of ViewState.
If you have ever looked at the source code of an ASP.NET page, you have certainly seen the serialized ViewState. You probably already know that ViewState is stored in a hidden field called _ViewState, in the form of a base64 string, because when someone tells how ViewState works, they usually start with that.
A small digression - before we figure out how ASP.NET creates this encoded string, we need to understand the hierarchy of controls on the page. Many experienced developers do not know that a page consists of a tree of controls, because all they have worked with is ASPX pages and controls declared on them ... but controls can have child elements that can contain their own child elements, and etc. This is how the tree of controls is formed, at the root of which the page itself lies. The second level is the elements declared on the top level of the ASPX page (as a rule, there are exactly three - Literal, containing everything up to the <form> tag, HtmlForm, which is a form and its children, and another Literal, which contains everything after the tag </ form>). On the third level are the controls contained in these elements, and so on. Each of these controls has its own ViewState — its own instance of the StateBag class. A protected method SaveViewState is declared in System.Web.UI.Control. It returns an object. Implementing Control.SaveViewState is just a call to the same method on your ViewState (StateBag also has a SaveViewState () method). By calling this method recursively on every control in the entire tree, ASP.NET builds another tree with the same structure, but now it is no longer a control tree, but a data tree.
The data in this step has not yet turned that string into a hidden field, it is just a tree of objects that need to be saved. And it is here that everything finally comes together ... are you ready? When a StateBag must save and return its state (StateBag.SaveViewState ()), it only does this for records that have been marked as dirty. This is why the StateBag is tracking it. This is the only reason. But what a reason! A StateBag could handle all the records contained in it, but why save data that has not been changed from its natural, declared state? There is absolutely no reason to process them - they will be restored anyway when ASP.NET parses the page, responding to the next request (in fact, the page is only parsed once, during this process a class is compiled, with which then ASP.NET and works). And despite this small optimization in ASP.NET, unnecessary data is still stored in ViewState due to improper use. Later I will show some examples of such errors.
Problem
If you read this far, my congratulations. Here is a reward for you. Suppose we have two almost identical ASPX forms, Page1.aspx and Page2.aspx. On each page only the form and Label:
< form id ="form1" runat ="server" >
< asp:Label id ="label1" runat ="server" Text ="" />
</ form >
* This source code was highlighted with Source Code Highlighter .
They are exactly the same, but there is a slight difference. On Page1.aspx, the Label text is simple - “abc”:
< asp:Label id ="label1" runat ="server" Text ="abc" />
* This source code was highlighted with Source Code Highlighter .
And on Page2.aspx the text is bigger, a lot more (preamble to the US Constitution):
< 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." />
* This source code was highlighted with Source Code Highlighter .
If you open Page1.aspx, you will only see “abc”. But you can see the HTML code of the page, and you will see the infamous _ViewState hidden field with a string of encrypted data. Note the size of this string. Now open the Page2.aspx page and you will see the preamble. Now open the source code of the page, and see what size the _ViewState field is here. Attention, question:
Are the sizes of these lines the same or not? Before you voice the answer, let's complicate the task a bit. Let's add a button to each page:
< asp:Button id ="button1" runat ="server" Text ="Postback" />
* This source code was highlighted with Source Code Highlighter .
No handler is hung on a click, so pressing a button only causes the page to "blink." Now repeat the experiment, but just before viewing the source code of the page, click on the button. The question is the same:
Are the sizes of these lines the same or not? The correct answer to the first part of the problem is: THEY EQUAL! They are the same, because none of these ViewState stores anything related to Labels at all. If you understand how ViewState works, this should be obvious. The Text property is assigned a declared value before the ViewState's tracking begins. This means that if you checked the Dirty Text flag in the StateBag, it would not be flagged. StateBag ignores untagged entries when SaveViewState () is called, so the Text property is not serialized to a hidden field. And since Text is not serialized, and the forms are otherwise absolutely identical, the size of the ViewState remains the same.
The correct answer to the second question is the same - they are the same! In order for the data to be recorded in ViewState, they must be marked as dirty. In order for the data to be marked as dirty, it must be changed after TrackViewState () is called. But even after we have completed the postback, ASP.NET handles server controls in the same way. The Text property is also set declaratively, as in the first query. No other manipulations were carried out with him, so it is not marked as dirty, even on the postback. Therefore, the size of the ViewState fields after postbacks remains the same.
Now we understand how ASP.NET determines what data to serialize. But we do not know how she serializes them. This topic is beyond the scope of this article (are you missing an assembly reference?), But if you're interested, read about
LosFormatter in ASP.NET 1.x or
ObjectStateFormatter in ASP.NET 2.0 .
And the last is deserialization. It's clear that all these cunning trackers and serialization would not be worthless if we could not get the data back. This topic is also beyond the scope of this article, suffice it to say that the whole process is directly opposite to the one discussed above. ASP.NET builds a tree of objects by reading the _ViewState field sent to the server, and deserializes it using LosFormatter (v1.x) or ObjectStateFormatter (v2.0).
4. Automatically recovers data
This is the last of the ViewState functions. The temptation to link it with the deserialization process just mentioned is great, but it is not part of this process. ASP.NET deserializes ViewState data, and THEN it fills the controls with this data. Many articles confuse these processes.
In the System.Web.UI.Control class (once again, the class from which all server and client controls inherit, including pages) inherits the LoadViewState () method, which accepts a parameter of type object. This is the opposite of SaveViewState (), which we have already discussed. Like SaveViewState (), LoadViewState () simply calls the same method on the StateBag object. And StateBag simply fills its collection of keys / values with data from the resulting object. If you are interested, the type of object obtained is System.Web.UI.Pair, a very simple class with two fields, First and Second. First is the ArrayList of the keys, and Second is the ArrayList of the values. StateBag just goes through these lists, and each time it runs this. Add (key, value). Here it is important to catch that the data passed to LoadViewState () are only those records that were marked as dirty
in the previous request . Even before loading records from ViewState, StateBag may already contain something in it, for example, data explicitly set by the developer before calling LoadViewState (). If some value passed to LoadViewState (), for some reason, has already appeared in the StateBag, it will be overwritten.
All this is pure magic of automatic state control. When a page starts loading during postback (even before initialization), all properties are assigned their natural default values. Then OnInit happens. In this phase, ASP.NET calls TrackViewState () on all StateBags. Then LoadViewState is called with deserialized data after the previous query. StateBag performs Add (key, value) for all this data. Well, since the tracking mechanism is already running, each value is marked as dirty, and it is again stored for the next postback. Brilliantly! Fuh. Now you are an expert on how ViewS works.
Incorrect use of ViewState
Now that we know how ViewState works, we can finally become aware of the problems that arise when it is used incorrectly. In this section, I will describe cases that show how many ASP.NET developers incorrectly handle the ViewState. But these are not obvious mistakes. Some of them relate to the nuances that will help you to further understand the whole mechanism.
Misuse cases
- Imposing default values
- Saving static data
- Saving cheap (hereinafter "cheap" - approx. Lane.) Data
- Program initialization of child controls
- Software initialization of dynamically created controls
1. Imposing default values
One of the most common mistakes, and fix it is also the easiest. The correct code is also usually smaller than the wrong one. Yes, yes, sometimes, doing everything correctly, you can do with less code. Imagine?
Usually this error is made when the developer of the control wants a certain property to have a certain default value, but at the same time either does not understand the tracking mechanism, or this mechanism is up to it. For example, let's imagine that the Text property must have a specific value that is stored in the session. 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 source code was highlighted with Source Code Highlighter .
The programmer committed a ViewState crime, somebody, call the ViewState's police! There are two major problems with this approach. First of all, since Joe is developing a control, and he spent the time writing the Text property, Joe probably wants other developers to be able to set some other value to this property. Jane, the page writer, is trying to do just that:
< abc:JoesControl id ="joe1" runat ="server" Text ="ViewState rocks!" />
* This source code was highlighted with Source Code Highlighter .
Jane will have a very bad day. So that it does not fit into this Text attribute, Joe’s control will not obey it. Poor Jane. It uses this control like any other control in ASP.NET, but this one works differently. The Joe control will overwrite the Text value that Jane asked! Moreover, since Joe wrote his code in OnLoad, in ViewState this value will be marked as dirty. So Jane brought on herself an increase in the ViewState serialized field, simply putting Joe's control on her page! Looks like Joe doesn't really like Jane. Maybe he just avenges her for something. Well, since we all know which sex rules this world (we can literally assumethat Jane made Joe fix her code. Joy to Jane, Joe eventually wrote this:
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 ; }
}
}
* This source code was highlighted with Source Code Highlighter .
See how much code has decreased. Joe doesn't even have to override OnLoad. Since the StateBag returns null, if it does not contain the key passed to it, Joe can check if his property has already been assigned a value by simply checking for null equality. If the equality is satisfied, it can safely return its beloved default value. If the equality is not satisfied, it will gladly return the assigned value. There is simply no place. And now, when Jane uses this control, she will not only get her Text attribute value, but the size of the ViewState on her page will not increase without a reason. It works better. Performance is better. Code - less. Total profit!
2. Saving static data
Here, as static, I understand such data that never changes, or does not change within the life cycle of a page, or even a user session. Suppose that Joe, our imaginary pseudo-programmer, was assigned the task of displaying the name of the current user at the top of each page of some eCommerce application. This is a good way to tell the user “Hey, we know you!” This gives users a sense of individuality and shows that the site is working as it should. Suppose that in this eCommerce application there is a business logic level API, which allows Joe to easily get the name of the currently authenticated user: CurrentUser.Name. Here is how Joe performs this 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);
}
* This source code was highlighted with Source Code Highlighter .
Of course, the current user name will be displayed. "A couple of trivia," Joe thinks. But we know what a misdeed the guy made. The Label control used by it already tracks the state of its ViewState at the moment when the Text user name is assigned to its Text property. This means that the user name will not only be displayed on the page, but also fall into the hidden ViewState field. Why force ASP.NET to do all these serialization and deserialization, if you overwrite this field anyway? Yes, it's just indecent! And even if he points out the problem, Joe just shrugs: “It's just a couple of bytes.” But even a couple of bytes can be easily saved. Solution one ... you just turn off ViewState for this Label.
< asp:Label id ="lblUserName" runat ="server" EnableViewState ="false" />
* This source code was highlighted with Source Code Highlighter .
Problem solved.
But there is a better solution. Label is one of the most commonly used controls, probably only Panel is more popular. The legs of this phenomenon grow from the experience of Visual Basic programmers. To show text on a VB form, you need a label. It is natural to assume that in ASP.NET WebForms Label performs a similar role, and if you need to display some text, you need to use Label. So, it is not. Label wraps its content in a tag. Ask yourself if you really need this tag? Unless you apply any styles to this text, the answer is most likely NO! This is enough for you:
<% = CurrentUser.Name %>
* This source code was highlighted with Source Code Highlighter .
You not only removed the extra code from the page (even if it was generated by the designer), but also arrived in the spirit of the code-behind model - separated the code from the design! If Joe has a special designer in charge of the look and feel of this eCommerce application, Joe will simply give him this task with the words “This is a job for a designer,” and he will be right. There is another reason why you can THINK you need a Label, namely if you need to do something with it in the code-behind. But here, ask yourself a question - do you need a Label? Let me introduce the most unpopular control in ASP.NET: Literal!
< asp:Literal id ="litUserName" runat ="server" EnableViewState ="false" />
* This source code was highlighted with Source Code Highlighter .
And no span tags.
3. Saving “cheap” data
This item includes the previous one. Static information is very easy to obtain. But not all easily accessible information is static. Sometimes we have such data that may change during the operation of the application, but it is still cheap to get to them. By “cheap,” I mean a minor search cost. A very common form of this error is to fill in the drop-down list of US states. Unless you write the application you are going to send on December 7, 1787 ( here), the list of states will not change in the foreseeable future. However, since all programmers simply hate hardcode, you probably don’t want to drive all these states into your page. But in case any state suddenly raises a rebellion (dreams, dreams), you don’t want to change your code. Our old friend Joe decided that he would fill out this drop-down list from the USSTATES database table. The site is already using the database, so everything is trivial - add a table and write a query to it:
< asp:DropdownList id ="lstStates" runat ="server" DataTextField ="StateName" DataValueField ="StateCode" />
* This source code was highlighted with Source Code Highlighter .
protected override void OnLoad( EventArgs args)
{
if (! this .IsPostback)
{
this .lstStates.DataSource = QueryDatabase();
this .lstStates.DataBind();
}
base .OnLoad(e);
}
* This source code was highlighted with Source Code Highlighter .
Like any ASP.NET control that works with data, Dropdown will use ViewState to remember its list of entries. At the moment there are exactly 50 states. Not only does each state in Dropdown store its ListItem object, so each state and its code will be serialized and recorded in ViewState. A whole bunch of data will constantly clog the channel every time the page loads, especially when it comes to dial-up connections. I often think, as I will explain to my grandmother that her Internet is so slow, because her computer lists all 50 states to the server. I do not think she will understand. Probably she will go into the explanation that in the days of her youth there were only 46 states. It looks like these 4 extra states are loading bandwidth. Damn these late states!
As in the case of static data, this problem can be solved simply by disabling the ViewState of the control. Unfortunately, this does not always work. This of course depends on the control with which you work, and those of its functions that you use. In our example, if Joe simply adds EnableViewState = “false”, and removes the if (! This.IsPostback) condition, he will successfully get rid of the extra data in ViewState, but will face another complication. Dropdown will no longer memorize the selected value after postback. STOP! This is another viewState myth. The reason that Dropdown can no longer remember its selected value is not in the fact that you turned off ViewState for it. Controls such as Dropwdon or Textbox can remember their current state even when ViewState is disabled.We Dropdown forgets the selected value, because you re-fill it every time on OnLoad, after he has restored his current state. The first thing he does when he receives new data is sending old data to the digital garbage bin. This means that when the user selects California, Dropdown will stubbornly return to it the default value (the first number of the list, if you have not changed anything). Fortunately, this problem has a simple solution - transfer the data loading to OnInit:Dropdown will stubbornly return to it the default value (the first number of the list, if you have not changed anything). Fortunately, this problem has a simple solution - transfer the data loading to OnInit:Dropdown will stubbornly return to it the default value (the first number of the list, if you have not changed anything). Fortunately, this problem has a simple solution - transfer the data loading to OnInit:
< asp:DropdownList id ="lstStates" runat ="server" DataTextField ="StateName" DataValueField ="StateCode" EnableViewState ="false" />
* This source code was highlighted with Source Code Highlighter .
protected override void OnInit( EventArgs args)
{
this .lstStates.DataSource = QueryDatabase();
this .lstStates.DataBind();
base .OnInit(e);
}
* This source code was highlighted with Source Code Highlighter .
A short explanation of why this works: you write data to the list before Dropdown tries to restore its state. Now this list will behave exactly as Joe intended, but a huge list of states will not be recorded in ViewState! Fine!
It is important to note that this rule applies to any data that is easy to obtain. You can argue that running into the database on each query is more expensive than storing data in ViewState. But I believe that in this case you will be wrong. Modern databases (say, SQL Server) use complex caching mechanisms, and can be very effective if they are properly configured. The list of states and so should be updated with any request, can not be helped. All that we have changed is that instead of sending it once again (for each request) over a slow, unreliable 56k connection thousands of miles away, we send it over the worst 10 Mbit LAN connection from your DB server to your Internet server. And if you really want to do optimization, you can cache the results of a database query. Countsort it out!
4. Software initialization of child controls
The harsh reality is this - you can't do everything declaratively. Sometimes sly logic comes into play. Actually, that's why we all have work, right? The problem is that ASP.NET doesn't provide an easy way to properlysoftware initialization of child controls. You can override OnLoad and do it there - but then you save the data that may not be stored in ViewState. You can override OnInit for the same purpose, but get the same problem. Remember that ASP.NET calls TrackViewState () in the OnInit phase. It does this recursively for the entire control tree, but BOTTOM UP! In other words, your OnInit is already AFTER OnInit your descendants. So at the moment when your OnInit starts, the child controls have already started tracking their ViewState! Let Joe want to display the current date and time in the Label declared on the form:
< asp:Label id ="lblDate" runat ="server" />
* This source code was highlighted with Source Code Highlighter .
protected override void OnInit( EventArgs args)
{
this .lblDate.Text = DateTime .Now.ToString( "MM/dd/yyyy HH:mm:ss" );
base .OnInit(e);
}
* This source code was highlighted with Source Code Highlighter .
And although Joe uses the earliest event that is available to him, it is already too late. The Label's ViewState already tracks its changes, and the current date and time will inevitably be recorded in ViewState. This example can be attributed to the above-described case of “cheap” data. Joe can just turn off ViewState from this Label. But here we solve another problem in order to illustrate an important principle. It would be great if Joe could declaratively set the text he needs. Sort of:
< asp:Label id ="Label1" runat ="server" Text ="<%= DateTime.Now.ToString() %>" />
* This source code was highlighted with Source Code Highlighter .
You may have already tried it. But ASP.NET will throw you right in the face of the fact that the syntax "<% =%>" cannot be used to set properties for server controls. Joe could use the syntax "<% #%>", but this approach will not differ from the data-binding method that we just discussed (disabling ViewState and filling with data on each request). It would be great if we could set the value of the property in the code, but at the same time allow the control to continue to work in its normal mode. Probably some code will work with this Label, and we would like all changes made by it to be saved in ViewState, as usual. For example, maybe Joe wants to give users the ability to turn off the current date on the page,and display an empty template instead:
private void cmdRemoveDate_Click( object sender, EventArgs args)
{
this .lblDate.Text = "--/--/---- --:--:--" ;
}
* This source code was highlighted with Source Code Highlighter .
If the user presses this button, the current date and time will disappear. But if we have already solved our problem by disabling ViewState'a, the date and time will magically appear again with the next postback, because disabling ViewState means that the new Label state will not be saved. Not good.
And what to do now?
What we really need is a declarative setting of a value that is not static, but is obtained as a result of certain operations. If it is set declaratively, Label will work as before - the initial state will not be saved, since it is set before tracking is turned on, and any changes will be recorded in ViewState. As I said, ASP.NET does not provide an easy way to accomplish this task. ASP.NET 2.0 developers can use the $ type syntax, which allows the use of expression builders to declare values that come from dynamic data sources (for example, resources, connection strings). But there is no “just execute this code” expression expression constructor, so this doesn't help you either (unless you use my CodeExpressionBuilder!) And ASP.NET 2.0 developers have OnPreInit. This is a great place to initialize the child controls, because this event occurs before OnInit, and therefore no ViewState still tracks its changes, but all controls have already been created. One problem - OnPreInit, unlike other events, is not recursive. So it is available only on the page itself. So this also does not help you if you write your control. It is bad that OnPreInit is not recursive, like OnInit, OnLoad and OnPreRender, I see no reason for such inconsistency. The problem is that we just want to set the value of the Label property before its ViewState starts tracking its state. We already know that the OnInit page is too late.Or maybe we can somehow wedge into the OnInit of the Label itself? In the code, we cannot hang up the event handler, because OnInit is the most previously possible place, but it is late. And this will not work in the constructor, because no child elements have been created yet. There are two ways out:
1. Declaratively subscribe to the Init event
< asp:Label id ="Label2" runat ="server" OnInit ="lblDate_Init" />
* This source code was highlighted with Source Code Highlighter .
This will work, since the OnInit attribute is processed before the Label's Init event fires, which gives us the opportunity to perform all the manipulations before tracking is enabled. In our example, the handler will simply set the Text property.
2. Write your control
public class DateTimeLabel : Label
{
public DateTimeLabel()
{
this .Text = DateTime .Now.ToString( "MM/dd/yyyy HH:mm:ss" );
}
}
* This source code was highlighted with Source Code Highlighter .
And then use it instead of the standard Label. Since the control itself initializes its state, it can do so before enabling tracking.
5. Software initialization of dynamically created controls
Here is the same problem as in the previous paragraph, but since you are in better control of the situation, it is much easier to solve it. Suppose Joe wrote his control, which at some point dynamically creates a Label:
public class JoesCustomControl : Control
{
protected override void CreateChildControls()
{
Label l = new Label();
this .Controls.Add(l);
l.Text = "Joe's label!" ;
}
}
* This source code was highlighted with Source Code Highlighter .
Hmmm
And when dynamically created controls start tracking? You can create and add to the page dynamically the controls at almost any moment, but ASP.NET includes a ViewState tracking on OnInit. Will our controls miss this event? No, they will not. The fact is that Controls.Add () is not just adding an item to the collection. This is something more.
As soon as a dynamically created control is added to the tree of elements, the root of which is the page, ASP.NET runs all missing events for this element and its descendants. Let's say you added a control to the PreRender event (although there are many reasons why it is better not to do this). At this point, the OnInit, LoadViewState, LoadPostBackData, and OnLoad events have already occurred. Immediately after the control enters the collection of controls on the page, all of these events work for it.
This means, my friends, that the tracking for the control will be turned on immediately after you add it to the page. In addition to the constructor, you can change the properties of controls on OnInit, and here the child elements already track changes to the ViewState. Joe adds everything in the CreateChildControls () method, which ASP.NET calls when it needs to make sure that the child controls exist (the time this method is called varies depending on whether you are implementing INamingContainer, whether the postback is being processed, Has anyone called EnsureChildControl ()). It can even be invoked on OnPreRender. But whenever this method is invoked, it will happen after or during the OnInit, and Joe will again stain ViewState. The solution here is elementary, but it's easy not to notice:
public class JoesCustomControl : Control
{
protected override void CreateChildControls()
{
Label l = new Label();
l.Text = "Joe's label!" ;
this .Controls.Add(l);
}
}
* This source code was highlighted with Source Code Highlighter .
It's simple - instead of having to initialize the Text property after adding a control to the collection, it does it before . This gives us confidence that Label has not yet enabled ViewState's tracking at the time of initialization. In fact, you can use this technique for something more complicated than just assigning properties. You can populate the data control before they become part of the control tree. Remember our example about the list of states? If we can create Dropdown dynamically, we can solve the problem without turning off 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);
}
}
* This source code was highlighted with Source Code Highlighter .
It works just fine. Dropdown will behave as if states are simply built-in list items. They are not written to ViewState, although ViewState is enabled for this control, which means that you can use all its functions dependent on ViewState, for example, the OnSelectedIndexChanged event. You can even do this with Grids, although it all depends on how you use them (you may have problems with sorting, paginating, etc.)
Be careful when working with ViewState
Now that you know all about ViewState's magic and how it interacts with the life cycle of a page in ASP.NET, it will be very easy for you to use ViewState carefully! Optimization of ViewState is very simple when you understand what is happening, and often this knowledge will be useful to you in order to reduce the amount of code written by you.