How can I decrease the size of the view state?
One convenience of ASP.NET controls is that they can preserve state
across postbacks -- a topic we've covered in depth in Chapter 6,
Maintaining State. This, of course, is a feature that comes at a price
-- to implement it, we add a hidden field to the page to store the
control settings for transmission between the client and server, but
depending on the controls the page uses, the view state can sometimes
become quite large.
One obvious way to reduce the size of view state is to turn it off
if you don't need it. This adjustment can be performed either at the
page level, or at the control level. If, for whatever reason, you can't
disable the view state (for example, your page uses controls that are
dependent upon the view state), there are a few other steps you can
take to at least reduce its impact on your page size.
Solutions
You have two options for reducing the impact that view state has on
your page size -- either compress the view state, or store it on the
server.
Compressing the View State
The following simple
CompressedViewStatePage
class implements basic GZIP compression on the page's view state. It reduced the size of the ViewState
object on my sample page from 20,442 bytes to 6,056 bytes -- an
impressive 70% reduction in size! Here's the class in all its glory:
Example 15.3. CompressedViewStatePage.cs (excerpt)
using System;
using System.IO.Compression;
using System.IO;
using System.Web.UI;
public class CompressedViewStatePage : System.Web.UI.Page
{
static public byte[] Compress(byte[] b)
{
MemoryStream ms = new MemoryStream();
GZipStream zs = new GZipStream(ms, CompressionMode.Compress);
zs.Write(b, 0, b.Length);
return ms.ToArray();
}
static public byte[] Decompress(byte[] b)
{
MemoryStream ms = new MemoryStream(b.Length);
ms.Write(b, 0, b.Length);
// last 4 bytes of GZipStream = length of decompressed data
ms.Seek(-4, SeekOrigin.Current);
byte[] lb = new byte[4];
ms.Read(lb, 0, 4);
int len = BitConverter.ToInt32(lb, 0);
ms.Seek(0, SeekOrigin.Begin);
byte[] ob = new byte[len];
GZipStream zs = new GZipStream(ms, CompressionMode.Decompress);
zs.Read(ob, 0, len);
return ob;
}
protected override object LoadPageStateFromPersistenceMedium()
{
byte[] b = Convert.FromBase64String(Request.Form["__VSTATE"]);
LosFormatter lf = new LosFormatter();
return lf.Deserialize(Convert.ToBase64String(Decompress(b)));
}
protected override void SavePageStateToPersistenceMedium(
object state
)
{
LosFormatter lf = new LosFormatter();
StringWriter sw = new StringWriter();
lf.Serialize(sw, state);
byte[] b = Convert.FromBase64String(sw.ToString());
ClientScript.RegisterHiddenField("__VSTATE",
Convert.ToBase64String(Compress(b)));
}
}
To use GZIP compression, simply inherit a specific page from the class, like this:
public partial class MyPage : CompressedViewStatePage
If you're worried about the performance implications of compressing
your view state, don't be. It's far more likely that bandwidth is a
greater bottleneck than CPU time on any given web server. Although
there are exceptions to this rule, the GZIP algorithm is blazingly fast
on the CPUs of today. Besides, if your server's CPU operates at 100%
all the time, you have far graver problems to worry about than the size
of a handful of pages.
This compression algorithm could also be implemented as an HTTP
module, which could then be applied to an entire site with a simple
Web.config modification. I suggest you try implementing this module as
an exercise, if you're keen. The MSDN article on building an HTTP module is a good place to start.
Storing View State on the Server
The second option for reducing view state's impact on page size is
to prevent view state data from being sent to the client altogether,
and instead store the data on the server.
The following
ServerViewStatePage
class allows us to use the Session object to store the view state:
Example 15.4. ServerViewStatePage.cs (excerpt)
using System;
using System.Web.UI;
using System.Configuration;
using System.IO;
public class ServerViewStatePage : System.Web.UI.Page
{
private const string _configKey = "ServerViewStateMode";
private const string _formField = "__SERVERVIEWSTATEKEY";
private string ViewStateData
{
get { return Request.Form[_formField]; }
set { ClientScript.RegisterHiddenField(_formField, value); }
}
private string PersistenceType
{
get { return (ConfigurationManager.AppSettings[_configKey]
?? "").ToLower(); }
}
private object ToObject(string viewstate)
{
byte[] b = Convert.FromBase64String(viewstate);
LosFormatter lf = new LosFormatter();
return lf.Deserialize(Convert.ToBase64String(b));
}
private string ToBase64String(object state)
{
LosFormatter lf = new LosFormatter();
StringWriter sw = new StringWriter();
lf.Serialize(sw, state);
byte[] b = Convert.FromBase64String(sw.ToString());
return Convert.ToBase64String(b);
}
private string ToSession(string value)
{
string key = Guid.NewGuid().ToString();
Session.Add(key, value);
return key;
}
private string FromSession(string key)
{
string value = Convert.ToString(Session[key]);
Session.Remove(key);
return value;
}
protected override object LoadPageStateFromPersistenceMedium()
{
switch (PersistenceType)
{
case "session":
return ToObject(FromSession(ViewStateData));
default:
return base.LoadPageStateFromPersistenceMedium();
}
}
protected override void SavePageStateToPersistenceMedium(
object ViewStateObject
)
{
switch (PersistenceType)
{
case "session":
ViewStateData = ToSession(ToBase64String(ViewStateObject));
break;
default:
base.SavePageStateToPersistenceMedium(ViewStateObject);
break;
}
}
}
To use
ServerViewStatePage
, simply inherit a specific page from this class, like this:public partial class MyPage : ServerViewStatePage
This class is configured via a single setting in
Web.config
: ServerViewStateMode
. Once you've configured this setting, you'll notice that the ViewState
object disappears from the page -- in its place is a simple ID that's
used to look up the contents of the page's view state on the server, in
the Session
object. If you feel uncomfortable storing view state in Session
,
this class could easily be extended to store view state wherever you
like -- in the file system, in the ASP.NET cache, or in a database.
As usual, there's no free lunch here. The decision to push view
state to the server and store it in Session has its own drawbacks. For
example, the Session object could be lost if the IIS worker process
recycles (a loss that does occur every so often in IIS, unless you've
disabled this default behavior). Furthermore, any change to the
underlying application files (such as editing
Web.config
,
or adding new binaries to the bin folder of your application) will also
cause the web application to recycle and Session data to be lost. And
if you use more than one web server (such as in a web farm environment)
you'll need to manage any shared session state that's stored in a
database.
No comments:
Post a Comment