Server Viewstate Revisited

Server-side Viewstate Shines ... Is It Really Gold?

CoreCoder

LANGUAGES: C#

ASP.NET VERSIONS: 2.0 | 3.5

 

Server Viewstate Revisited

Server-side Viewstate Shines ... Is It Really Gold?

 

By Dino Esposito

 

Too many times in the ASP.NET classes I teach around the world, I end up getting a question or two about viewstate. Getting these questions is not a bad thing, per se, as there are so many areas in the viewstate topic that are worth deeper investigation. What regularly strikes me, though, is the overall tone of these viewstate-related questions. People appear to have a hate-only relationship with viewstate, and see it uniquely as a source of trouble. At the same time, they implicitly seem to blame the ASP.NET team for inventing viewstate and for keeping it around, version after version. I just want to strike a blow for the poor ASP.NET viewstate here.

 

ASP.NET pages and controls use the viewstate to build a call context and retain values across two successive requests for the same page. The viewstate represents the state of the page when it was last processed on the server. The state is persisted usually, but not necessarily, on the client side and is restored before the page request is processed. The viewstate guarantees that ASP.NET server controls automatically retain their state each time their host page is processed on the server. If ASP.NET developers don t have to worry about the state of their controls, this is because of the viewstate. In summary, the viewstate is one of the most important features of ASP.NET and a key element behind most of the magic of the Web Forms model.

 

At the same time, used without strict criteria, the viewstate easily can become a burden for pages. My opinion is that viewstate is not pure evil; and even if you want to consider it as evil, then it is a necessary evil. You certainly can do your programming work without viewstate, but viewstate is behind the extremely convenient programming model that made ASP.NET a success.

 

Where should you keep your pages viewstate? As mentioned, the viewstate is saved to a hidden field and roundtripped with each request. The size of the viewstate is added to the HTTP packet, thus requiring a much larger bandwidth. But is storing the viewstate on the server really an option?

 

What Is Viewstate and Why Does It Exist?

Since its first appearance, ASP.NET made a point of offering a rich programming model entirely based on server controls. It added an extra and richer abstraction layer on top of classic ASP intrinsic objects (Request, Response, Server), which formed an abstraction layer already, but on top of the HTTP transportation protocol. An ASP.NET page is made of server components that propound a declarative programming model persisted to an ASP.NET markup language the actual content of ASPX files.

 

The implicit contract signed between developers and the ASP.NET runtime states that developers operate on stateful server controls. How can stateful ASP.NET controls work on top of a stateless protocol such as HTTP?

 

Two subsequent ASP.NET requests follow one another in a pure stateless manner. Each control persisted to an ASPX file is instantiated at each page request and has its last known good state restored just before the page-specific code can execute. This last known good state must be persisted somewhere. This state is what the ASP.NET jargon names the viewstate .

 

To summarize, the viewstate exists in ASP.NET to back up the Web Forms postback model and to make it possible for developers to declare the output of their pages using code and/or markup, as in the following code snippet:

 

void Page_Load(object sender, EventArgs e)

{

   if (!IsPostBack)

       TextBox1.Text = "Hello";

}

 

The Text property of the TextBox control is set only the first time that the page is accessed in the session. In any subsequent requests, though, the Text property retains its last assigned value, regardless of the fact that it belongs to a control instance that has just been instantiated. Figure 1 shows the workflow according to which the state of page controls is restored.

 


Figure 1: The page-internal workflow.

 

Compared to classic ASP, the ASP.NET programming model makes it easier to write Web pages and makes it look a lot like standard server programming. There s no need to know about HTML or JavaScript; you simply need to learn a programming model and stick to that.

 

The Viewstate Is Heavyweight

So ASP.NET pushes a control-based programming model. Each server control has its own viewstate; that is, its own bag of properties to persist somewhere in a way that survives page postbacks. The viewstate of all page controls is then merged into a single data collection and, in a series of steps, serialized to a Base64 string. This string represents the current state of the page. Two questions are looking for an answer. First, where would you store the viewstate? Second, how large is it? The two questions are obviously connected. The larger the state, the more critical is the choice of the persistence mechanism. After a long debate, the ASP.NET team opted for saving the viewstate as a hidden field in the same page that consumes it. In this way, the viewstate represents a sort of call context for the page. At the same time, roundtripping a page gets significantly more expensive than in classic ASP because of the additional field. The viewstate is uploaded to the server and downloaded to the client. Moreover, the viewstate is totally ignored on the client; it is downloaded to the client only to be uploaded back to the server on the next page postback. Sounds weird, doesn t it? Why can t you simply leave the viewstate on the server? In fact, you can modify the behavior of ASP.NET pages to force them to leave the viewstate on the server. However, it can likely be more pain than gain.

 

The Viewstate on the Server

The way in which you customize viewstate persistence is different in ASP.NET 1.x and newer versions. The Page class features two viewstate-related protected methods in all versions of ASP.NET, as shown here:

 

protected virtual void

   SavePageStateToPersistenceMedium(object state);

protected virtual object

   LoadPageStateFromPersistenceMedium();

 

In ASP.NET 1.x, you simply had to override both methods and load and save the viewstate graph you receive to the persistence medium of choice. For example, the save method may contain the following code to save the viewstate data off to a disk file:

 

protected override void

   SavePageStateToPersistenceMedium(object state)

{

   string file = GetFileName();

   StreamWriter writer = new StreamWriter(file);

   LosFormatter formatter = new LosFormatter();

   formatter.Serialize(writer, state);

   writer.Close();

   return;

}

 

The LoadPageStateFromPersistenceMedium method will simply figure out the file name to load from, then deserialize and return the content to the page framework. It is completely up to you to choose the naming convention to use for server files where the viewstate is stored. This is a major issue that I will return to later. For now, suffice it to say that the issue remains even if you opt for saving the viewstate to a database or in memory in Session or Cache. In all cases, you need to figure out a unique key for the data that can be guessed during postback, based on the HTTP context.

 

Changes in ASP.NET 2.0 and ASP.NET 3.5

The two aforementioned methods also exist in newer versions of ASP.NET, but they are now part of a significantly refactored framework that supports control state in addition to viewstate. As a result, you no longer must override LoadPageStateFromPersistenceMedium and SavePageStateToPersistenceMedium. These two methods now have been moved up in the call stack, and leverage other methods for the physical persistence job. Figure 2 illustrates the current behavior of SavePageStateToPersistenceMedium.

 

protected virtual void SavePageStateToPersistenceMedium(object state)

{

   PageStatePersister pageStatePersister = this.PageStatePersister;

   if (state is Pair)

   {

       Pair pair = (Pair) state;

       pageStatePersister.ControlState = pair.First;

       pageStatePersister.ViewState = pair.Second;

   }

   else

   {

       pageStatePersister.ViewState = state;

   }

   pageStatePersister.Save();

}

Figure 2: SavePageStateToPersistenceMedium in ASP.NET 2.0 and 3.5.

 

As of ASP.NET 2.0 and ASP.NET 3.5, the persistence is managed by a page persister object that you reach programmatically through the PageStatePersister protected property of the Page class:

 

protected virtual PageStatePersister

   PageStatePersister {get; }

 

All you must do is derive your page class from System.Web.UI.Page and override the property to make it return an instance of the persister class to use. This is demonstrated in Figure 3.

 

public class ServerViewStatePage : Page

{

 private PageStatePersister _persister = null;

 public ServerViewStatePage()

 {

 }

 protected override PageStatePersister PageStatePersister

 {

   get

   {

     if (this._persister == null)

     {

       this._persister = new MyOwnPageStatePersister(this);

     }

     return this._persister;

   }

 }

}

Figure 3: A custom page that saves viewstate on the server.

 

The persister class is responsible for loading and saving viewstate. By default, ASP.NET uses a class named HiddenFieldPageStatePersister a public class in the System.Web assembly. This class inherits from a public abstract class named PageStatePersister. Figure 4 shows the contract of the persister class.

 

public abstract class PageStatePersister

{

   // Fields

   private object _controlState;

   private Page _page;

   private IStateFormatter _stateFormatter;

   private object _viewState;

   // Methods

   protected PageStatePersister(Page page);

   public abstract void Load();

   public abstract void Save();

   // Properties

   public object ControlState { get; set; }

   protected Page Page { get; set; }

   protected IStateFormatter StateFormatter { get; }

   public object ViewState { get; set; }

}

Figure 4: The contract of the PageStatePersister class.

 

By deriving a custom class from PageStatePersister, you take control of the viewstate and control state persistence. In Figure 3, I used a sample class named MyOwnPageStatePersister that saves page state off to a server-side disk file. The full source code for the custom page state persister is shown in Figure 5. As you can see, the persister base class holds in inherited members both the viewstate and control state collections. You call the system-provided formatter to serialize collections to a Base64 string and then save it off wherever you feel like. In the sample code for this article, I save the viewstate to a disk file whose name contains the session ID and the page URL (see end of article for download details).

 

public class MyOwnPageStatePersister : PageStatePersister

{

   public MyOwnPageStatePersister(Page page) : base(page)

   {

   }

   public override void Load()

   {

       string pageState = String.Empty;

       // Get viewstate from a file

       string file = GetFileName();

       StreamReader reader = new StreamReader(file);

       pageState = reader.ReadToEnd();

       reader.Close();

       // Attach viewstate to the page

       if (!string.IsNullOrEmpty(pageState))

       {

           Pair pair = (Pair)base.StateFormatter.Deserialize(pageState);

           base.ViewState = pair.First;

           base.ControlState = pair.Second;

       }

   }

   public override void Save()

   {

       string pageState = String.Empty;

       // Get control and view state

       if ((base.ViewState != null) || (base.ControlState != null))

       {

           pageState = base.StateFormatter.Serialize(new Pair(base.ViewState,

                                                     base.ControlState));

       }

       // Save it off somewhere

       string file = GetFileName();

       StreamWriter writer = new StreamWriter(file);

       writer.Write(pageState);

       writer.Close();

       return;

   }

   private string GetFileName()

   {

       string url = base.Page.Request.ServerVariables["Path_Info"];

       url = url.Replace("/", "_");

       // Place the file in a temp folder (with write permissions)

       string fileName = "{0}/{1}_{2}.viewstate";

       fileName = String.Format(fileName, "ViewStateTemp",

          base.Page.Session.SessionID, url);

       return base.Page.Server.MapPath(fileName);

   }

}

Figure 5: A custom persister class using a disk file.

 

Drawbacks

The previous code works as long as the application has a non-empty session state. In ASP.NET, a new session ID is generated for each request until any page writes something to the session. So, using the session ID as the key to retrieve the viewstate from the file system or any database is risky.

 

The main issue, however, has only to do with the most common scenario. What if a user requests the same page several times in the same session? Each time you create a new file or record. As long as the naming convention or the key is based on session and URL, you end up overwriting the old information. Is this a problem? Yes. What, therefore, if the user navigates back to the previously visited page? Where s the viewstate of this instance of the same page?

 

If a client-hidden field is used, the viewstate is part of the page that lives in the browser cache and can be easily retrieved and served. If a server-side solution is adopted, it is up to you to manage this issue.

 

I ll flesh this out and come to an effective solution in my June column.

 

Files accompanying this article are available for download.

 

Dino Esposito specializes mainly in ASP.NET, AJAX, and RIA solutions and is the author of the upcoming Programming ASP.NET 3.5 Core Reference (Microsoft Press, 2008). He also wrote Introducing ASP.NET AJAX, also for Microsoft Press. Late-breaking news is available at http://weblogs.asp.net/despos.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish