Powered by Blogger.

Tuesday, August 17, 2010

Speed Up Your Site! 8 ASP.NET Performance Tips (part 2)

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

  ©Template by Dicas Blogger.

TOPO