ViewState's Real Understanding
- Transfer
From a translator: This is a translation of an article from one of the ASP.NET developers, which details the mechanism for managing page state - ViewState. Despite the fact that the article was written in 2006, it still has not lost its relevance.
ViewState is a very strange creature. I’ll try to put an end to all kinds of misunderstandings, and try to explain how ViewState’s mechanism actually works, from beginning to end, looking at it from various points of view.
There are many articles whose authors try to dispel the myths about ViewState. You might even think that this is all about fighting windmills (where ViewState is windmills and the Internet is a fight tool). But, I will inform you, the mills have not stopped yet. Quite the opposite, they spin and fill your living room. It’s time to hit them again. Don’t worry, no windmill has been harmed in this article.
It’s not that there weren’t any good sources of information about the ViewState, they just all miss something, and thus only contribute to the general turmoil around the ViewState. For example, one of the key points is understanding how ViewState’s tracking works. And herea very good and in-depth article about ViewState that doesn't even cover this! Or here is an article on W3Schools that suggests that the form parameters sent to the server are stored in the ViewState, which is not true. (Do not believe me? Disable ViewState textbox on their example and run it again). And here is the MSDN documentation that describes how controls store their state between postbacks. You can’t 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 understood that everything that is shoved in the ViewState will be sent to the client. NOT TRUE! It’s clear where this turmoil around ViewState comes from. Nowhere on the Internet could I find a 100% complete and accurate description of his work! The best article I've seen is an article by Scott Mitchell . 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 misunderstandings 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 covering the white spots left by other articles. After a detailed description of the ViewState process, I will show a few errors that developers make when using ViewState, usually without even realizing it, and how to fix these errors. It should be noted that I wrote this article based on ASP.NET 1.x. However, there are very few changes to the ViewState engine in ASP.NET 2.0. In particular, a new type of ViewState has appeared - ControlState, however it is used in the same way as ViewState, so we can simply ignore it.
First let me explain why I think that understanding the essence of ViewState is very important:
Now, let's start from the very beginning.
It is even more important to understand what ViewState does NOT.
Despite the fact that ViewState has one main goal in the ASP.NET Framework, the four roles 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 bunch of information about ViewState that confuses people. Hopefully now we have broken ViewState into more digestible parts.
If you've ever used hash tables, you already know everything. Nothing extraordinary, ViewState has an indexer that takes a string as a key and any object as a value. For instance:
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 not to be defined in the same place 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 a third time: Server controls use ViewState to store most, if not all, of their properties. Based on your experience, thinking about properties, you probably imagine something like:
It is important to know that most of the properties in ASP.NET controls do not look like this. Instead, they use ViewState, not a hidden variable:
And once again I focus your attention - this is true for almost ALL PROPERTIES, even Style (generally speaking Style does this by implementing 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 you can and what cannot be dynamically changed on postbacks. But this is a slightly different topic for discussion. It is also important to understand how the default values are implemented within this technique . When you think of the usual properties with default values, you probably imagine something like:
The default value is such, since it is the property that returns it if it has never been assigned anything. How do we achieve the same behavior, but using ViewState? That's how:
Like a hash table, a StateBag returns null by key if it does not contain an entry with that key. That is, if the value of the record is null, then it was not created and it is necessary to return the default value, otherwise we return the value of the record. The most attentive must have noticed the difference between the two implementations. In the case of ViewState, setting the property to null will send the property back to the default state. And if you assign null to a regular property, then it will simply contain null. This is one of the reasons why ASP.NET constantly uses String.Empty ("") rather than null. This is also not very important for ready-made controls, since their properties, which may be null, already have a default value of null. All I can say is keep this in mind when writing your controls. And finally although ViewState is used to store property values, it is not limited to this. In a control or on a 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 remember some information in this way, but this, again, is a topic for another conversation.
Has it ever happened to you that you appropriated some value to the property of the control and then you felt as if ... 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 mask the smell. Honestly! Alright, alright, setting properties doesn't make you dirty. But she makes a dirty StateBag! StateBag is not just a dumb collection of keys and values, like a hash table (just don’t tell the hash table what I called it, it is scary in anger). In addition to storing key values, StateBag can track changes (hereinafter “tracking” - approx. Per.). Tracking is either on or off. It can be enabled by calling TrackViewState (), but if enabled, you can’t turn it off anymore. WHEN AND ONLY WHEN tracking is ON, any change to any StateBag record will mark this record as “Dirty” (hereinafter “dirty” - approx. Per.). The StateBag has even a special method that is needed for verification, and the record is dirty - IsItemDirty (string key). You can also manually mark a record by calling the SetItemDirty (string key) method. To illustrate this, let's assume that we have an untracked StateBag:
Generally speaking, tracking allows the StateBag to keep track of which records have changed since 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 the record as dirty, even if the assigned value matches the current one!
One could write the ViewState so that it checks the new and old values before determining how to flag the record. But remember that ViewState allows you to use any objects as values, which means that we are not talking about trivial string comparisons, and not every object implements IComparable. Alas, since there is serialization and deserialization, the object that you put in the ViewState will not be the same object after postback. ViewState does not need such comparisons, so it doesn’t. That's what tracking is about.
But you are probably wondering why the StateBag needs this feature. Why would someone suddenly need to know if there have been any changes after TrackViewState () has been called? Why not just use the regular collection and not know the problems? And this is one of the places that cause all the confusion around the ViewState. I conducted interviews with many ASP.NET specialists who have years of using this technology in their resumes, and they could not prove that they understand this issue. Moreover, I have never interviewed a person who knew this! First of all, in order to understand why tracking is necessary, 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:
Here is the Label that is defined in your file. The next thing to understand is the ability of ASP.NET to bind attributes to control properties. When ASP.NET parses the file and detects the tag with runat = server, it creates an instance of the declared control. She names the corresponding variable according to what value you set for the ID attribute (by the way, not everyone knows that ID is optional, ASP.NET will generate it by itself. This may be useful, but now it's not about that). 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 whether the control has the appropriate property and assignhim declared value. Of course, the attribute value is declared as a string (in the end, it is declared in the markup text 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 (TypeConverter's and Parse static methods are used here). It is enough that the conversion somehow happens, 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 dirty. And if tracking is disabled, entries will not be marked. The question is - when ASP.NET calls the setter of a property that matches the declared attribute, does the StateBag track changes? The answer is no, it does not track, because tracking does not begin until someone calls TrackViewState (), and ASP.NET does this in the page initialization phase. This technique makes it easy to distinguish between values specified declaratively from dynamically specified values. If you still do not understand how important this is, please continue reading.
Apart from the process of creating ASP.NET declarative controls, the first two considered ViewState functions 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’ll talk about how ASP.NET uses these properties of the StateBag to bring to life the (black) magic of ViewState.
If you've ever looked at the source code of an ASP.NET page, you certainly saw a serialized ViewState. You probably already know that the ViewState is stored in a hidden field called _ViewState, in the form of a base64 string, because when someone tells how the ViewState works, they usually start from this.
A small digression - before we understand 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 the page consists of a tree of controls, because all they have worked with are ASPX pages and the controls declared on them ... but controls can have children that can contain their children, and etc. This forms a tree of controls, at the root of which lies the page itself. The second level is the elements declared at the top level of the ASPX page (usually there are exactly three of them - Literal, containing everything up to the tag) At the third level are the controls contained in the mentioned elements, and so on. Each of these controls has its own ViewState - its own instance of the StateBag class. A protected SaveViewState method is declared in System.Web.UI.Control. It returns an object. Implementation of Control.SaveViewState is just a call of the same method to its ViewState (the StateBag also has a SaveViewState () method). By calling this method recursively on each control in the whole 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 at this step has not yet turned that line into a hidden field, it is just a tree of objects that need to be saved. And then everything finally finally comes together ... are you ready? When a StateBag needs to save and return its state (StateBag.SaveViewState ()), it only does this for records that have been marked as dirty. That's what StateBag is tracking for. This is the only reason. But what a reason! A StateBag could handle all the records it contains, but why save data that has not changed compared to 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, parsing the page occurs only once, in the process, the class is compiled, with which then ASP. NET and works). And despite this small optimization in ASP.NET, unnecessary data is still stored in the ViewState due to misuse. Later I will show some examples of such errors.
If you read up to this place - my congratulations. Here's a reward for you. Suppose we have two almost identical ASPX forms, Page1.aspx and Page2.aspx. On each page there is only a form and a Label:
They are exactly the same, but there is a slight difference. Label’s text on Page1.aspx is simple - “abc”:
And on Page2.aspx the text is larger, much larger (preamble to the US Constitution):
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 notorious hidden field _ViewState with a string of encrypted data. Pay attention to the size of this line. Now open the Page2.aspx page and you will see the preamble. Now open the page source and see what size the _ViewState field is here. Attention, question: Are the sizes of these lines the same or not? Before voicing the answer, let's complicate the task a bit. Let's add a button to each page:
No handler is hung on the click, so clicking on the button only makes the page “blink”. Now repeat the experiment, but just before looking at the source code of the page, click on the button. The same question: Are the sizes of these lines the same or not?The correct answer to the first part of the problem is as follows - THEY ARE THE SAME! They are the same because none of these ViewStates have anything to do with Labels at all. If you understand how ViewState works, this should be obvious. The Text property is assigned the declared value before the tracking of the ViewState begins. This means that if you checked the Dirty flag of the Text entry in the StateBag, it would not be flagged. The StateBag ignores unlabeled entries when SaveViewState () is called, so the Text property is not serialized to a hidden field. And since Text is not serialized, and in all other respects the forms are absolutely identical, the ViewState's size remains the same.
The correct answer to the second question is the same - THEY ARE SAME! In order for data to be written to ViewState, it must be marked as dirty. In order for data to be marked as dirty, it must be modified after TrackViewState () is called. But even after we performed postback, ASP.NET handles server controls in exactly the same way. The Text property is set as declaratively as in the first request. No other manipulations were carried out with him, so it is not marked as dirty, even on postback. Therefore, the size of the ViewState fields after postbacks remains the same.
Now we understand how ASP.NET determines what data needs to be serialized. 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 thing here is deserialization. It’s clear that all these tricky tracking and serialization would have been worthless if we couldn’t 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 deserializing it using LosFormatter (v1.x) or ObjectStateFormatter (v2.0).
This is the last of the functions of ViewState. It is tempting to link it to the deserialization process just mentioned, but this is not part of this process. ASP.NET deserializes the ViewState data, and then IT populates the controls with that data. Many articles confuse these processes.
In the System.Web.UI.Control class (once again, the class from which all server and client controls, including pages, are inherited), the LoadViewState () method is defined, which accepts a parameter of type object. This is the opposite of the SaveViewState () method that we already discussed. Like SaveViewState (), LoadViewState () simply calls the same method on the StateBag object. And StateBag just fills its collection of keys / values with data from the received object. If you're interested, the type of the resulting object is System.Web.UI.Pair, a very simple class with two fields, First and Second. First is the ArrayList of keys, and Second is the ArrayList of values. StateBag just goes through these lists, and each time it executes this.Add (key, value). It’s important to catch that the data passed to LoadViewState () is only those records that were marked as dirtyat the previous request . Even before loading records from the ViewState, the StateBag may already contain something, for example, data explicitly specified by the developer before calling LoadViewState (). If some value passed to LoadViewState () is already in the StateBag for some reason, it will be overwritten.
All this is pure magic of automatic state management. When the page starts loading during postback (even before initialization), all properties are assigned their natural default values. Then happens OnInit. In this phase, ASP.NET calls TrackViewState () on all StateBags. Then LoadViewState is called with deserialized data after the previous request. 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 saved for the next postback. Brilliantly! Fuh. Now you are an expert on ViewState’s work.
Misuse of ViewState
Now that we know how ViewState works, we can finally recognize the problems that arise when it is used incorrectly. In this part, I will describe cases that show how many ASP.NET developers mistreat ViewState. But these are not obvious errors. Some of them relate to the nuances that will help you further understand this whole mechanism.
One of the most common mistakes, and fixing it is also the easiest. The correct code is also usually more compact than the wrong one. Yes, yes, sometimes, doing everything right, you can do with less code. Can you 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 easy for him. 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:
The programmer committed a ViewState crime, somebody call the ViewState’s police! There are two serious problems with this approach. First of all, since Joe is developing the control, and he took the time to write the Text property, Joe probably wants other developers to be able to set some other value for this property. Jane writing the page just does this and tries:
Jane will have a very bad day. So that she does not fit into this Text attribute, the Joe control does not obey her. Poor Jane. It uses this control like any other ASP.NET control, but this one works differently. The Joe control will overwrite the Text value that Jane set! Moreover, since Joe wrote his code in OnLoad, in ViewState this value will be marked as dirty. So Jane has also incurred an increase in the serialized ViewState field by simply putting the Joe control on her page! It seems that Joe is not very kind to Jane. Maybe he just takes revenge on her for something. Well, since we all know which sex rules this world (literally, “we all know which sex rules this world” - approx. Per.), We can assume that Jane made Joe fix her code. To Jane's joy, Joe eventually wrote this:
See how much code is reduced. Joe doesn't even have to redefine OnLoad. Since the StateBag returns null if it does not contain the key passed to it, Joe can check to see if its property has already been assigned by simply checking for null. If equality holds, he can safely return his darling default value. If equality is not satisfied, he happily returns the assigned value. Nowhere is easier. And now, when Jane uses this control, she will not only get her Text attribute value, but the ViewState’s size on her page will not increase for no reason. It works - better. Performance is better. The code is less. The sheer gain!
Here, by static, I mean data that never changes, or does not change within the life cycle of a page, or even a user session. Suppose that Joe, our imagined woe-programmer, was given 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 “Aegeus, we know you!” This gives users a sense of individuality and shows that the site is working as it should. Suppose this eCommerce application has a business logic level API that allows Joe to easily get the name of the currently authenticated user: CurrentUser.Name. Here's how Joe accomplishes this task:
Of course, the name of the current user will be shown. A couple of little things, Joe thinks. But we know what kind of misconduct the guy committed. The Label control used by him already monitors the state of its ViewState at the time of assigning its username to the Text property. This means that the username will not only be displayed on the page, but also will fall into the hidden ViewState field. Why force ASP.NET to do all these serialization and deserialization if you rewrite this field anyway? Yes, it's just indecent! And even if he points out a problem, Joe simply shrugs: “This is just a couple of bytes.” But even a couple of bytes can be easily saved. The solution is the first ... you just disable ViewState for this Label.
The problem is resolved. But there is a better solution. Label is one of the most commonly used controls, probably only Panel is more popular than it. The legs of this phenomenon grow out of the experience of Visual Basic programmers. To display text in VB form, you need a Label. It is natural to assume that in ASP.NET WebForms Label has a similar role, and if you need to display some text, you need to use Label. So, this is not so. Label wraps its contents 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 quite enough for you:
You not only removed the extra code from the page (even if generated by the designer), but also acted in the spirit of code-behind models - you separated the code from the design! If Joe has a special designer in charge of the appearance of this eCommerce application, Joe will simply hand over this task to him with the words “This is a job for the designer,” and he will be right. There is another reason why you can THINK that you need a Label, namely if you need to do something with it in the code behind. But then ask yourself a question - do you need a Label? Let me introduce the most unpopular control in ASP.NET: Literal!
And no span tags.
This item includes the previous one. Static information is very easy to obtain. But not all readily available information is static. Sometimes we have such data that can change during the operation of the application, but it is still cheap to reach them. By "cheap" I mean the insignificant cost of a search. A very common variation of this error is filling out a drop-down list of US states. Unless you write an application that you intend to ship on December 7, 1787 ( here), the list of states will not change in the foreseeable future. However, since all programmers just hate hardcode, you probably don't want to drive all these states into your page. But in the event that suddenly some state raises an uprising (dreams, dreams), you do not want to change your code. Our old friend Joe decided that he would populate this drop-down list from the USSTATES database table. The site already uses the database, so everything is trivial - add a table and write a query to it:
Like any ASP.NET control that works with data, Dropdown will use ViewState to remember its list of records. There are currently exactly 50 states. Not only is there a ListItem object for each state in Dropdown, but each state and its code will be serialized and written to ViewState. A whole bunch of data will constantly clog the channel every time a page loads, especially when it comes to dial-up connections. I often think how I will explain to my grandmother that her Internet is so slow because her computer lists all 50 states to the server. I don’t think she will understand. Perhaps she will set about explaining that at the time of her youth there were only 46 states. It looks like these 4 extra states are loading bandwidth. Damn these late states!
As with static data, this problem can be solved simply by disabling the ViewState's control. Unfortunately, this does not always work. This of course depends on the control you are working with and the functions you use. In our example, if Joe simply adds EnableViewState = “false” and removes the if (! This.IsPostback) condition, he will successfully get rid of extra data in the ViewState, but will face another complication. Dropdown will no longer remember the selected value after postback. STOP! This is another myth about ViewState. The reason that Dropdown can no longer remember its selected value is not because you disabled ViewState for it. Controls such as Dropwdon or Textbox can remember their current state even when ViewState is disabled. Dropdown forgets the selected value from us, because you fill it again every time on OnLoad, after it has restored its current state. The first thing he does when he receives new data is to send the old ones to a digital trash. This means that when the user selects California, Dropdown will stubbornly return the default value to it (the first number of the list, if you did not change anything). Fortunately, this problem has a simple solution - transfer data loading to OnInit: Dropdown will stubbornly return it to its default value (the first list number if you haven’t changed anything). Fortunately, this problem has a simple solution - transfer data loading to OnInit: Dropdown will stubbornly return it to its default value (the first list number if you haven’t changed anything). Fortunately, this problem has a simple solution - transfer data loading to OnInit:
A short explanation of why this works: you write data to a list before Dropdown tries to restore its state. Now this list will behave exactly as Joe intended, but the huge list of states will not be recorded in ViewState! Excellent! It is important to note that this rule applies to any data that is easy to obtain. You might argue that running to the database on every request 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 sophisticated caching mechanisms, and can be very effective if configured correctly. The list of states should be updated anyway for any request, nothing can be done. All that we changed is instead of sending it once again (for each request) on a slow one, unreliable 56k connection for thousands of miles, we send it in the worst case 10-megabit LAN connection from your database server to your Internet server. And if you really want to do optimization, you can cache the results of a query to the database. Consider, understand!
The harsh reality is that you can’t do anything declaratively. Sometimes tricky logic comes into play. Actually, that’s why we all have work, right? The problem is that ASP.NET does not provide an easy way to properlyprogrammatically initialize child controls. You can override OnLoad and do it there - but then you save the data that may not be stored in the 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 happens AFTER the OnInit of your descendants. So at the moment your OnInit starts, the child controls have already started tracking their ViewStates! Let Joe want to display the current date and time in a Label declared on the form:
And although Joe uses the earliest event that is available to him, it is already too late. ViewState Label is already tracking its changes, and the current date and time will inevitably be written to ViewState. This example can be attributed to the case of “cheap” data described above. Joe can simply disable ViewState from this Label. But here we are solving another problem to illustrate an important principle. It would be great if Joe could declaratively set the text he needed. Sort of:
You may have tried it already. But ASP.NET will strike you straight in the face with the fact that the syntax "<% =%>" cannot be used to set the properties of server controls. Joe could use the syntax "<% #%>", but this approach will not be any different from the data binding method we just discussed (disabling the ViewState and filling in the data on each request). It would be great if we could set the property value 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 the changes made by it to be saved in ViewState, as usual. For example, maybe Joe wants to give users the option to turn off the display of the current date on the page,
If the user clicks this button, the current date and time will disappear. But if we already solved our problem by disabling ViewState, the date and time will magically occur again at the next postback, because disabling ViewState means that Label’s new state will not be saved. Not good. So what to do now?
What we really need is to declaratively set a value that is not static, but obtained as a result of certain operations. If it is set declaratively, the Label will work as before - the initial state will not be saved, because it was set before tracking was enabled, and any changes will be recorded in the ViewState. As I said, ASP.NET does not provide an easy way to complete this task. ASP.NET 2.0 developers are offered $ -type syntax that allows the use of expression builders to declaratively set values that come from dynamic data sources (e.g. resources, connection strings). But there is no constructor of expressions like “just execute this code”, so this will not help you either (unless you use my CodeExpressionBuilder!). ASP.NET 2.0 developers also have OnPreInit. This is a great place to initialize child controls, because this event occurs before OnInit and therefore no ViewState has tracked its changes yet, but all controls are already created. One problem - OnPreInit, unlike other events, is not recursive. So it is available only on the page itself. So this will not help you either, if you write your control. It's bad that OnPreInit is not recursive like OnInit, OnLoad and OnPreRender, I see no reason for such inconsistency. The essence of 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 OnInit pages are too late. Or maybe we can somehow wedge into OnInit Label itself? We cannot hang an event handler in the code, because the earliest time it is possible is OnInit, and this is late. And this cannot be done in the constructor, because no child elements have yet been created. There are two ways out:
1. Declaratively sign up for the Init event
This will work, since the OnInit attribute is processed before the Label's Init event is triggered, which gives us the opportunity to carry out all the manipulations before tracking is enabled. In our example, the Handler will simply set the Text property.
2. Write your control
And then use it instead of the standard Label. Since the control itself initializes its state, it can do this before enabling tracking.
This 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. Suppose Joe wrote his own control, which dynamically creates a Label at some point:
Hmmm. And when do dynamically created controls start tracking? You can create and add controls to the page dynamically at almost any time, but ASP.NET includes ViewState's tracking on OnInit. Won't our controls miss this event? No, they won’t miss it. The fact is that Controls.Add () is not just adding an item to a collection. This is something more. As soon as a dynamically created control is added to the element tree, the root of which is the page, ASP.NET fires all the missing events for this element and its descendants. Say you added a control to a PreRender event (although there are many reasons why it’s best not to). At this point, the OnInit, LoadViewState, LoadPostBackData, and OnLoad events have already occurred. Right after
This means, my friends, that tracking for the control will be enabled immediately after you add it to the page. In addition to the constructor, you can change the properties of the controls on OnInit, and here the child elements already track the ViewState's changes. Joe adds everything to the CreateChildControls () method, which ASP.NET calls when she needs to make sure that the child controls exist (the moment of calling this method varies depending on whether you are implementing the INamingContainer, whether postback processing is underway, Has anyone called EnsureChildControl ()). It can even be called on OnPreRender. But whenever this method is called, it will happen after or during OnInit, and Joe will again get ViewState dirty. The solution here is elementary, but it is easy not to notice:
It's simple - instead of initializing the Text property after adding the control to the collection, it does this before . This gives us confidence that Label has not yet enabled ViewState tracking at the time of initialization. In fact, you can use this trick for something more complicated than just assigning properties. You can populate a data control before it becomes part of the control tree. Remember our state list example? If we can create Dropdown dynamically, we can solve the problem without disconnecting ViewState:
It works just fine. Dropdown will behave as if the states are just built-in list items. They are not written to the ViewState, although the ViewState is included for this control, which means that you can use all of its functions depending on the ViewState, for example, the OnSelectedIndexChanged event. You can do this even with grids, although it all depends on how you use them (you may have problems sorting, paginating, etc.)
Now that you know everything about ViewState’s magic and how it interacts with the page life cycle in ASP.NET, it’s very easy for you to use ViewState carefully! Optimization of ViewState is very simple when you understand what is happening, and often this knowledge is also useful to you in order to reduce the amount of code written by you.
ViewState is a very strange creature. I’ll try to put an end to all kinds of misunderstandings, and try to explain how ViewState’s mechanism actually works, from beginning to end, looking at it from various points of view.
There are many articles whose authors try to dispel the myths about ViewState. You might even think that this is all about fighting windmills (where ViewState is windmills and the Internet is a fight tool). But, I will inform you, the mills have not stopped yet. Quite the opposite, they spin and fill your living room. It’s time to hit them again. Don’t worry, no windmill has been harmed in this article.
It’s not that there weren’t any good sources of information about the ViewState, they just all miss something, and thus only contribute to the general turmoil around the ViewState. For example, one of the key points is understanding how ViewState’s tracking works. And herea very good and in-depth article about ViewState that doesn't even cover this! Or here is an article on W3Schools that suggests that the form parameters sent to the server are stored in the ViewState, which is not true. (Do not believe me? Disable ViewState textbox on their example and run it again). And here is the MSDN documentation that describes how controls store their state between postbacks. You can’t 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 understood that everything that is shoved in the ViewState will be sent to the client. NOT TRUE! It’s clear where this turmoil around ViewState comes from. Nowhere on the Internet could I find a 100% complete and accurate description of his work! The best article I've seen is an article by Scott Mitchell . 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 misunderstandings 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 covering the white spots left by other articles. After a detailed description of the ViewState process, I will show a few errors that developers make when using ViewState, usually without even realizing it, and how to fix these errors. It should be noted that I wrote this article based on ASP.NET 1.x. However, there are very few changes to the ViewState engine in ASP.NET 2.0. In particular, a new type of ViewState has appeared - ControlState, however it is used in the same way as ViewState, so we can simply ignore it.
First let me explain why I think that understanding the essence of ViewState is very important:
A misunderstanding of ViewState leads to ...
- Loss of important information
- Attacks on ViewState
- Poor performance - up to NO PERFORMANCE
- Bad extensibility - how many users can you serve if each of them sends 50k with each request?
- Bad design in general
- Headache, nausea, dizziness and irreversible distortion of the shape of the superciliary arches.
Now, let's start from the very beginning.
What does ViewState do?
This is a list of the main functions of ViewState. Each of them serves a specific purpose. Further we will consider how exactly they achieve these goals.
- Saves key control data as a hash table
- Monitors ViewState Status Changes
- Serializes and deserializes the stored data into a hidden field on the client
- Automatically restores data on postbacks
It is even more important to understand what ViewState does NOT.
What does ViewState not do?
- Automatically saves the state of class fields (hidden, protected or open)
- Remember any information when loading the page (only postbacks)
- Eliminates the need to load data on every request
- It is responsible for downloading 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 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 bunch of information about ViewState that confuses people. Hopefully now we have broken ViewState into more digestible parts.
1. ViewState saves data
If you've ever used hash tables, you already know everything. Nothing extraordinary, ViewState has an indexer that takes a string as a key and any object as a value. For instance:
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 not to be defined in the same place 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 a third time: Server controls use ViewState to store most, if not all, of their properties. Based on your experience, thinking about 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 do not look like this. Instead, they use ViewState, not 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 focus your attention - this is true for almost ALL PROPERTIES, even Style (generally speaking Style does this by implementing 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 you can and what cannot be dynamically changed on postbacks. But this is a slightly different topic for discussion. It is also important to understand how the default values are implemented within this technique . When you think of the usual properties with 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 do 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, a StateBag returns null by key if it does not contain an entry with that key. That is, if the value of the record is null, then it was not created and it is necessary to return the default value, otherwise we return the value of the record. The most attentive must have noticed the difference between the two implementations. In the case of ViewState, setting the property to null will send the property back to the default state. And if you assign null to a regular property, then it will simply contain null. This is one of the reasons why ASP.NET constantly uses String.Empty ("") rather than null. This is also not very important for ready-made controls, since their properties, which may be null, already have a default value of null. All I can say is keep this in mind when writing your controls. And finally although ViewState is used to store property values, it is not limited to this. In a control or on a 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 remember some information in this way, but this, again, is a topic for another conversation.
2. ViewState tracks changes
Has it ever happened to you that you appropriated some value to the property of the control and then you felt as if ... 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 mask the smell. Honestly! Alright, alright, setting properties doesn't make you dirty. But she makes a dirty StateBag! StateBag is not just a dumb collection of keys and values, like a hash table (just don’t tell the hash table what I called it, it is scary in anger). In addition to storing key values, StateBag can track changes (hereinafter “tracking” - approx. Per.). Tracking is either on or off. It can be enabled by calling TrackViewState (), but if enabled, you can’t turn it off anymore. WHEN AND ONLY WHEN tracking is ON, any change to any StateBag record will mark this record as “Dirty” (hereinafter “dirty” - approx. Per.). The StateBag has even a special method that is needed for verification, and the record is dirty - IsItemDirty (string key). You can also manually mark a record 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 the StateBag to keep track of which records have changed since 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 the record as dirty, even if the assigned value matches 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 flag the record. But remember that ViewState allows you to use any objects as values, which means that we are not talking about trivial string comparisons, and not every object implements IComparable. Alas, since there is serialization and deserialization, the object that you put in the ViewState will not be the same object after postback. ViewState does not need such comparisons, so it doesn’t. That's what tracking is about.
But you are probably wondering why the StateBag needs this feature. Why would someone suddenly need to know if there have been any changes after TrackViewState () has been called? Why not just use the regular collection and not know the problems? And this is one of the places that cause all the confusion around the ViewState. I conducted interviews with many ASP.NET specialists who have years of using this technology in their resumes, and they could not prove that they understand this issue. Moreover, I have never interviewed a person who knew this! First of all, in order to understand why tracking is necessary, 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:
* 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 bind attributes to control properties. When ASP.NET parses the file and detects the tag with runat = server, it creates an instance of the declared control. She names the corresponding variable according to what value you set for the ID attribute (by the way, not everyone knows that ID is optional, ASP.NET will generate it by itself. This may be useful, but now it's not about that). 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 whether the control has the appropriate property and assignhim declared value. Of course, the attribute value is declared as a string (in the end, it is declared in the markup text 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 (TypeConverter's and Parse static methods are used here). It is enough that the conversion somehow happens, 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 dirty. And if tracking is disabled, entries will not be marked. The question is - when ASP.NET calls the setter of a property that matches the declared attribute, does the StateBag track changes? The answer is no, it does not track, because tracking does not begin until someone calls TrackViewState (), and ASP.NET does this in the page initialization phase. This technique makes it easy to distinguish between values specified declaratively from dynamically specified values. If you still do not understand how important this is, please continue reading.
3. Serialization and deserialization
Apart from the process of creating ASP.NET declarative controls, the first two considered ViewState functions 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’ll talk about how ASP.NET uses these properties of the StateBag to bring to life the (black) magic of ViewState.
If you've ever looked at the source code of an ASP.NET page, you certainly saw a serialized ViewState. You probably already know that the ViewState is stored in a hidden field called _ViewState, in the form of a base64 string, because when someone tells how the ViewState works, they usually start from this.
A small digression - before we understand 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 the page consists of a tree of controls, because all they have worked with are ASPX pages and the controls declared on them ... but controls can have children that can contain their children, and etc. This forms a tree of controls, at the root of which lies the page itself. The second level is the elements declared at the top level of the ASPX page (usually there are exactly three of them - Literal, containing everything up to the tag) At the third level are the controls contained in the mentioned elements, and so on. Each of these controls has its own ViewState - its own instance of the StateBag class. A protected SaveViewState method is declared in System.Web.UI.Control. It returns an object. Implementation of Control.SaveViewState is just a call of the same method to its ViewState (the StateBag also has a SaveViewState () method). By calling this method recursively on each control in the whole 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 at this step has not yet turned that line into a hidden field, it is just a tree of objects that need to be saved. And then everything finally finally comes together ... are you ready? When a StateBag needs to save and return its state (StateBag.SaveViewState ()), it only does this for records that have been marked as dirty. That's what StateBag is tracking for. This is the only reason. But what a reason! A StateBag could handle all the records it contains, but why save data that has not changed compared to 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, parsing the page occurs only once, in the process, the class is compiled, with which then ASP. NET and works). And despite this small optimization in ASP.NET, unnecessary data is still stored in the ViewState due to misuse. Later I will show some examples of such errors.
Task
If you read up to this place - my congratulations. Here's a reward for you. Suppose we have two almost identical ASPX forms, Page1.aspx and Page2.aspx. On each page there is only a form and a Label:
They are exactly the same, but there is a slight difference. Label’s text on Page1.aspx is simple - “abc”:
* This source code was highlighted with Source Code Highlighter.
And on Page2.aspx the text is larger, much larger (preamble to the US Constitution):
* 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 notorious hidden field _ViewState with a string of encrypted data. Pay attention to the size of this line. Now open the Page2.aspx page and you will see the preamble. Now open the page source and see what size the _ViewState field is here. Attention, question: Are the sizes of these lines the same or not? Before voicing the answer, let's complicate the task a bit. Let's add a button to each page:
* This source code was highlighted with Source Code Highlighter.
No handler is hung on the click, so clicking on the button only makes the page “blink”. Now repeat the experiment, but just before looking at the source code of the page, click on the button. The same question: Are the sizes of these lines the same or not?The correct answer to the first part of the problem is as follows - THEY ARE THE SAME! They are the same because none of these ViewStates have anything to do with Labels at all. If you understand how ViewState works, this should be obvious. The Text property is assigned the declared value before the tracking of the ViewState begins. This means that if you checked the Dirty flag of the Text entry in the StateBag, it would not be flagged. The StateBag ignores unlabeled entries when SaveViewState () is called, so the Text property is not serialized to a hidden field. And since Text is not serialized, and in all other respects the forms are absolutely identical, the ViewState's size remains the same.
The correct answer to the second question is the same - THEY ARE SAME! In order for data to be written to ViewState, it must be marked as dirty. In order for data to be marked as dirty, it must be modified after TrackViewState () is called. But even after we performed postback, ASP.NET handles server controls in exactly the same way. The Text property is set as declaratively as in the first request. No other manipulations were carried out with him, so it is not marked as dirty, even on postback. Therefore, the size of the ViewState fields after postbacks remains the same.
Now we understand how ASP.NET determines what data needs to be serialized. 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 thing here is deserialization. It’s clear that all these tricky tracking and serialization would have been worthless if we couldn’t 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 deserializing it using LosFormatter (v1.x) or ObjectStateFormatter (v2.0).
4. Automatically restores data
This is the last of the functions of ViewState. It is tempting to link it to the deserialization process just mentioned, but this is not part of this process. ASP.NET deserializes the ViewState data, and then IT populates the controls with that data. Many articles confuse these processes.
In the System.Web.UI.Control class (once again, the class from which all server and client controls, including pages, are inherited), the LoadViewState () method is defined, which accepts a parameter of type object. This is the opposite of the SaveViewState () method that we already discussed. Like SaveViewState (), LoadViewState () simply calls the same method on the StateBag object. And StateBag just fills its collection of keys / values with data from the received object. If you're interested, the type of the resulting object is System.Web.UI.Pair, a very simple class with two fields, First and Second. First is the ArrayList of keys, and Second is the ArrayList of values. StateBag just goes through these lists, and each time it executes this.Add (key, value). It’s important to catch that the data passed to LoadViewState () is only those records that were marked as dirtyat the previous request . Even before loading records from the ViewState, the StateBag may already contain something, for example, data explicitly specified by the developer before calling LoadViewState (). If some value passed to LoadViewState () is already in the StateBag for some reason, it will be overwritten.
All this is pure magic of automatic state management. When the page starts loading during postback (even before initialization), all properties are assigned their natural default values. Then happens OnInit. In this phase, ASP.NET calls TrackViewState () on all StateBags. Then LoadViewState is called with deserialized data after the previous request. 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 saved for the next postback. Brilliantly! Fuh. Now you are an expert on ViewState’s work.
Misuse of ViewState
Now that we know how ViewState works, we can finally recognize the problems that arise when it is used incorrectly. In this part, I will describe cases that show how many ASP.NET developers mistreat ViewState. But these are not obvious errors. Some of them relate to the nuances that will help you further understand this whole mechanism.
Misuse cases
- Imposing a Default Value
- Saving Static Data
- Saving cheap (hereinafter “cheap” - approx. Per.) Data
- Programmatically Initializing Child Controls
- Programmatically Initializing Dynamically Created Controls
1. Imposing a default value
One of the most common mistakes, and fixing it is also the easiest. The correct code is also usually more compact than the wrong one. Yes, yes, sometimes, doing everything right, you can do with less code. Can you 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 easy for him. 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 serious problems with this approach. First of all, since Joe is developing the control, and he took the time to write the Text property, Joe probably wants other developers to be able to set some other value for this property. Jane writing the page just does this and tries:
* This source code was highlighted with Source Code Highlighter.
Jane will have a very bad day. So that she does not fit into this Text attribute, the Joe control does not obey her. Poor Jane. It uses this control like any other ASP.NET control, but this one works differently. The Joe control will overwrite the Text value that Jane set! Moreover, since Joe wrote his code in OnLoad, in ViewState this value will be marked as dirty. So Jane has also incurred an increase in the serialized ViewState field by simply putting the Joe control on her page! It seems that Joe is not very kind to Jane. Maybe he just takes revenge on her for something. Well, since we all know which sex rules this world (literally, “we all know which sex rules this world” - approx. Per.), We can assume that Jane made Joe fix her code. To Jane's joy, 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 is reduced. Joe doesn't even have to redefine OnLoad. Since the StateBag returns null if it does not contain the key passed to it, Joe can check to see if its property has already been assigned by simply checking for null. If equality holds, he can safely return his darling default value. If equality is not satisfied, he happily returns the assigned value. Nowhere is easier. And now, when Jane uses this control, she will not only get her Text attribute value, but the ViewState’s size on her page will not increase for no reason. It works - better. Performance is better. The code is less. The sheer gain!
2. Saving static data
Here, by static, I mean data that never changes, or does not change within the life cycle of a page, or even a user session. Suppose that Joe, our imagined woe-programmer, was given 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 “Aegeus, we know you!” This gives users a sense of individuality and shows that the site is working as it should. Suppose this eCommerce application has a business logic level API that allows Joe to easily get the name of the currently authenticated user: CurrentUser.Name. Here's how Joe accomplishes this task:
(ShoppingCart.aspx)
(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 name of the current user will be shown. A couple of little things, Joe thinks. But we know what kind of misconduct the guy committed. The Label control used by him already monitors the state of its ViewState at the time of assigning its username to the Text property. This means that the username will not only be displayed on the page, but also will fall into the hidden ViewState field. Why force ASP.NET to do all these serialization and deserialization if you rewrite this field anyway? Yes, it's just indecent! And even if he points out a problem, Joe simply shrugs: “This is just a couple of bytes.” But even a couple of bytes can be easily saved. The solution is the first ... you just disable ViewState for this Label.
* This source code was highlighted with Source Code Highlighter.
The problem is resolved. But there is a better solution. Label is one of the most commonly used controls, probably only Panel is more popular than it. The legs of this phenomenon grow out of the experience of Visual Basic programmers. To display text in VB form, you need a Label. It is natural to assume that in ASP.NET WebForms Label has a similar role, and if you need to display some text, you need to use Label. So, this is not so. Label wraps its contents 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 quite 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 generated by the designer), but also acted in the spirit of code-behind models - you separated the code from the design! If Joe has a special designer in charge of the appearance of this eCommerce application, Joe will simply hand over this task to him with the words “This is a job for the designer,” and he will be right. There is another reason why you can THINK that you need a Label, namely if you need to do something with it in the code behind. But then ask yourself a question - do you need a Label? Let me introduce the most unpopular control in ASP.NET: Literal!
* 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 readily available information is static. Sometimes we have such data that can change during the operation of the application, but it is still cheap to reach them. By "cheap" I mean the insignificant cost of a search. A very common variation of this error is filling out a drop-down list of US states. Unless you write an application that you intend to ship on December 7, 1787 ( here), the list of states will not change in the foreseeable future. However, since all programmers just hate hardcode, you probably don't want to drive all these states into your page. But in the event that suddenly some state raises an uprising (dreams, dreams), you do not want to change your code. Our old friend Joe decided that he would populate this drop-down list from the USSTATES database table. The site already uses the database, so everything is trivial - add a table and write a query to it:
* 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 records. There are currently exactly 50 states. Not only is there a ListItem object for each state in Dropdown, but each state and its code will be serialized and written to ViewState. A whole bunch of data will constantly clog the channel every time a page loads, especially when it comes to dial-up connections. I often think how I will explain to my grandmother that her Internet is so slow because her computer lists all 50 states to the server. I don’t think she will understand. Perhaps she will set about explaining that at the time of her youth there were only 46 states. It looks like these 4 extra states are loading bandwidth. Damn these late states!
As with static data, this problem can be solved simply by disabling the ViewState's control. Unfortunately, this does not always work. This of course depends on the control you are working with and the functions you use. In our example, if Joe simply adds EnableViewState = “false” and removes the if (! This.IsPostback) condition, he will successfully get rid of extra data in the ViewState, but will face another complication. Dropdown will no longer remember the selected value after postback. STOP! This is another myth about ViewState. The reason that Dropdown can no longer remember its selected value is not because you disabled ViewState for it. Controls such as Dropwdon or Textbox can remember their current state even when ViewState is disabled. Dropdown forgets the selected value from us, because you fill it again every time on OnLoad, after it has restored its current state. The first thing he does when he receives new data is to send the old ones to a digital trash. This means that when the user selects California, Dropdown will stubbornly return the default value to it (the first number of the list, if you did not change anything). Fortunately, this problem has a simple solution - transfer data loading to OnInit: Dropdown will stubbornly return it to its default value (the first list number if you haven’t changed anything). Fortunately, this problem has a simple solution - transfer data loading to OnInit: Dropdown will stubbornly return it to its default value (the first list number if you haven’t changed anything). Fortunately, this problem has a simple solution - transfer data loading to OnInit:
* 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 a list before Dropdown tries to restore its state. Now this list will behave exactly as Joe intended, but the huge list of states will not be recorded in ViewState! Excellent! It is important to note that this rule applies to any data that is easy to obtain. You might argue that running to the database on every request 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 sophisticated caching mechanisms, and can be very effective if configured correctly. The list of states should be updated anyway for any request, nothing can be done. All that we changed is instead of sending it once again (for each request) on a slow one, unreliable 56k connection for thousands of miles, we send it in the worst case 10-megabit LAN connection from your database server to your Internet server. And if you really want to do optimization, you can cache the results of a query to the database. Consider, understand!
4. Software initialization of child controls
The harsh reality is that you can’t do anything declaratively. Sometimes tricky logic comes into play. Actually, that’s why we all have work, right? The problem is that ASP.NET does not provide an easy way to properlyprogrammatically initialize child controls. You can override OnLoad and do it there - but then you save the data that may not be stored in the 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 happens AFTER the OnInit of your descendants. So at the moment your OnInit starts, the child controls have already started tracking their ViewStates! Let Joe want to display the current date and time in a Label declared on the form:
* 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. ViewState Label is already tracking its changes, and the current date and time will inevitably be written to ViewState. This example can be attributed to the case of “cheap” data described above. Joe can simply disable ViewState from this Label. But here we are solving another problem to illustrate an important principle. It would be great if Joe could declaratively set the text he needed. Sort of:
* This source code was highlighted with Source Code Highlighter.
You may have tried it already. But ASP.NET will strike you straight in the face with the fact that the syntax "<% =%>" cannot be used to set the properties of server controls. Joe could use the syntax "<% #%>", but this approach will not be any different from the data binding method we just discussed (disabling the ViewState and filling in the data on each request). It would be great if we could set the property value 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 the changes made by it to be saved in ViewState, as usual. For example, maybe Joe wants to give users the option to turn off the display of the current date on the page,
private void cmdRemoveDate_Click(object sender, EventArgs args)
{
this.lblDate.Text = "--/--/---- --:--:--";
}
* This source code was highlighted with Source Code Highlighter.
If the user clicks this button, the current date and time will disappear. But if we already solved our problem by disabling ViewState, the date and time will magically occur again at the next postback, because disabling ViewState means that Label’s new state will not be saved. Not good. So what to do now?
What we really need is to declaratively set a value that is not static, but obtained as a result of certain operations. If it is set declaratively, the Label will work as before - the initial state will not be saved, because it was set before tracking was enabled, and any changes will be recorded in the ViewState. As I said, ASP.NET does not provide an easy way to complete this task. ASP.NET 2.0 developers are offered $ -type syntax that allows the use of expression builders to declaratively set values that come from dynamic data sources (e.g. resources, connection strings). But there is no constructor of expressions like “just execute this code”, so this will not help you either (unless you use my CodeExpressionBuilder!). ASP.NET 2.0 developers also have OnPreInit. This is a great place to initialize child controls, because this event occurs before OnInit and therefore no ViewState has tracked its changes yet, but all controls are already created. One problem - OnPreInit, unlike other events, is not recursive. So it is available only on the page itself. So this will not help you either, if you write your control. It's bad that OnPreInit is not recursive like OnInit, OnLoad and OnPreRender, I see no reason for such inconsistency. The essence of 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 OnInit pages are too late. Or maybe we can somehow wedge into OnInit Label itself? We cannot hang an event handler in the code, because the earliest time it is possible is OnInit, and this is late. And this cannot be done in the constructor, because no child elements have yet been created. There are two ways out:
1. Declaratively sign up for the Init event
* This source code was highlighted with Source Code Highlighter.
This will work, since the OnInit attribute is processed before the Label's Init event is triggered, which gives us the opportunity to carry out 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 this before enabling tracking.
5. Software initialization of dynamically created controls
This 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. Suppose Joe wrote his own control, which dynamically creates a Label at some point:
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 do dynamically created controls start tracking? You can create and add controls to the page dynamically at almost any time, but ASP.NET includes ViewState's tracking on OnInit. Won't our controls miss this event? No, they won’t miss it. The fact is that Controls.Add () is not just adding an item to a collection. This is something more. As soon as a dynamically created control is added to the element tree, the root of which is the page, ASP.NET fires all the missing events for this element and its descendants. Say you added a control to a PreRender event (although there are many reasons why it’s best not to). At this point, the OnInit, LoadViewState, LoadPostBackData, and OnLoad events have already occurred. Right after
This means, my friends, that tracking for the control will be enabled immediately after you add it to the page. In addition to the constructor, you can change the properties of the controls on OnInit, and here the child elements already track the ViewState's changes. Joe adds everything to the CreateChildControls () method, which ASP.NET calls when she needs to make sure that the child controls exist (the moment of calling this method varies depending on whether you are implementing the INamingContainer, whether postback processing is underway, Has anyone called EnsureChildControl ()). It can even be called on OnPreRender. But whenever this method is called, it will happen after or during OnInit, and Joe will again get ViewState dirty. The solution here is elementary, but it is 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 initializing the Text property after adding the control to the collection, it does this before . This gives us confidence that Label has not yet enabled ViewState tracking at the time of initialization. In fact, you can use this trick for something more complicated than just assigning properties. You can populate a data control before it becomes part of the control tree. Remember our state list example? If we can create Dropdown dynamically, we can solve the problem without disconnecting 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 the states are just built-in list items. They are not written to the ViewState, although the ViewState is included for this control, which means that you can use all of its functions depending on the ViewState, for example, the OnSelectedIndexChanged event. You can do this even with grids, although it all depends on how you use them (you may have problems sorting, paginating, etc.)
Be careful when working with ViewState
Now that you know everything about ViewState’s magic and how it interacts with the page life cycle in ASP.NET, it’s very easy for you to use ViewState carefully! Optimization of ViewState is very simple when you understand what is happening, and often this knowledge is also useful to you in order to reduce the amount of code written by you.