5. Keeping the View State on the Server
As discussed so far, there’s one major reason to
keep the view state off the client browser. The more stuff you pack
into the view state, the more time the page takes to download and upload
because the view state is held in a hidden field. It is important to
note that the client-side hidden field is not set in stone, but is
simply the default storage medium where the view state information can
be stored. Let’s see how to proceed to save the view state on the Web
server in the sample file.
Important
Before we go any further
in discussing the implementation of server-side view state, there’s one
key aspect you should consider. You should guarantee that the correct
view-state file will be served to each page instance the user retrieves
via the browser’s history. This is not an issue as long as each page
contains its own view state. But when the view state is stored
elsewhere, unless you want to disable Back/Forward functionality, you
should provide a mechanism that serves the “right” view state for the
instance of a given page that the user is reclaiming. At a minimum, you
need to make copies of the view state for about 6 to 8 instances. As you
can see, what you save in the roundtrip is lost in server’s memory or
server-side I/O operations. All in all, keeping the view state on the
client and inside of the page is perhaps the option that works better in
the largest number of scenarios. If the view state is a problem, you
have only one way out: reducing its size. |
The LosFormatter Class
To design an alternative storage scheme for the
view state, we need to put our hands on the string that ASP.NET stores
in the hidden field. The string will be saved in a server-side file and
read from where the page is being processed. The class that ASP.NET 1.x
actually uses to serialize and deserialize the view state is LosFormatter.
The LosFormatter class has a simple programming interface made of only two publicly callable methods—Serialize and Deserialize. The Serialize method writes the final Base64 representation of the view state to a Stream or TextWriter object:
public void Serialize(Stream stream, object viewState);
public void Serialize(TextWriter output, object viewState);
The Deserialize method builds an object from a stream, a TextReader object, or a plain Base64 string:
public object Deserialize(Stream stream);
public object Deserialize(TextReader input);
public object Deserialize(string input);
The LosFormatter
class is the entry point in the view-state internal mechanism. By using
it, you can be sure everything will happen exactly as in the default
case and, more importantly, you’re shielded from any other details.
The ObjectStateFormatter Class
As mentioned, the LosFormatter class is replaced by ObjectStateFormatter starting with ASP.NET 2.0. LosFormatter is still available for compatibility reasons, however. The new serializer writes to a binary writer, whereas LosFormatter writes to a text writer. LosFormatter needs to turn everything to a string for storage, while ObjectStateFormatter capitalizes on the underlying binary model and writes out just bytes. As a result, ObjectStateFormatter
serializes the same object graph into roughly half the size, and spends
about half as much time in the serialization and deserialization
process.
The Serialize method of ObjectStateFormatter writes the final Base64 representation of the view state to a Stream or a string object:
public string Serialize(Object graph);
public void Serialize(Stream stream, Object graph);
The Deserialize method of ObjectStateFormatter builds an object from a stream or a plain Base64 string:
public object Deserialize(Stream stream);
public object Deserialize(string input);
Creating a View-State-Less Page
The Page class
provides a couple of protected virtual methods that the run time uses
when it needs to deserialize or serialize the view state. The methods
are named LoadPageStateFromPersistenceMedium and SavePageStateToPersistenceMedium:
protected virtual void SavePageStateToPersistenceMedium(object viewState);
protected virtual object LoadPageStateFromPersistenceMedium();
If you override both methods, you can load and
save view-state information from and to anything. In particular, you can
use a storage medium that is different from the hidden field used by
the default implementation. Because the methods are defined as protected
members, the only way to redefine them is by creating a new class and
making it inherit from Page. The following code gives you an idea of the default behavior of LoadPageStateFromPersistenceMedium:
string m_viewState = Request.Form["__VIEWSTATE"];
ObjectStateFormatter m_formatter = new ObjectStateFormatter();
StateBag viewStateBag = m_formatter.Deserialize(m_viewState);
The structure of the page we’re going to create is as follows:
public class ServerViewStatePage : Page
{
protected override
object LoadPageStateFromPersistenceMedium()
{ ... }
protected override
void SavePageStateToPersistenceMedium(object viewState)
{ ... }
}
Saving the View State to a Web Server File
The tasks accomplished by the SavePageStateToPersistenceMedium
method are easy to explain and understand. The method takes a string as
an argument, opens the output stream, and calls into the LosFormatter serializer:
protected override
void SavePageStateToPersistenceMedium(object viewStateBag)
{
string file = GetFileName();
StreamWriter sw = new StreamWriter(file);
ObjectStateFormatter m_formatter = new ObjectStateFormatter();
m_formatter.Serialize(sw, viewStateBag);
sw.Close();
return;
}
private string GetFileName()
{
// Return the desired filename.
...
}
How should we choose the name of the file to
make sure that no conflicts arise? The view state is specific to a page
request made within a particular session. So the session ID and the
request URL are pieces of information that can be used to associate the
request with the right file. Alternatively, you could give the view
state file a randomly generated name and persist it in a custom hidden
field within the page. Note that in this case you can’t rely on the
__VIEWSTATE hidden field because when overriding the methods, you alter
the internal procedure that would have created it.
The GetFileName function in the preceding code could easily provide the file a unique name according to the following pattern:
Notice that for an ASP.NET application to
create a local file, you must give the ASP.NET account special
permissions on a file or folder. I suggest creating a new subfolder to
contain all the view-state files. Deleting files for expired sessions
can be a bit tricky, and a Windows service is probably the tool that
works best. A Windows service, after all, can auto-start on reboot and
because it runs autonomously from the ASP.NET application it can clean
out files in any case.
Loading the View State from a Web Server File
In our implementation, the LoadPageStateFromPersistenceMedium method determines the name of the file to read from, extracts the Base64 string, and calls ObjectStateFormatter to deserialize:
protected override object LoadPageStateFromPersistenceMedium()
{
object viewStateBag;
string file = GetFileName();
try {
StreamReader sr = new StreamReader(file);
string m_viewState = sr.ReadToEnd();
sr.Close();
}
catch {
throw new HttpException("The View State is invalid.");
}
ObjectStateFormatter m_formatter = new ObjectStateFormatter();
try {
viewStateBag = m_formatter.Deserialize(m_viewState);
}
catch {
throw new HttpException("The View State is invalid.");
}
return viewStateBag;
}
To take advantage of this feature to keep view
state on the server, you only need to change the parent class of the
page code file to inherit from ServerViewStatePage:
public partial class TestPage : ServerViewStatePage
{
}
Figure 3 shows the view-state files created in a temporary folder on the Web server machine.
The page shown in Figure 3 enables view state, but no hidden field is present in its client-side code.