ASP.Net: Reduce Page Size, and Make Pages Load Faster by Storing View State on Server

July 6, 2010 | By Rakhitha | Filed in: .Net Stuff, Tech.

View state is a mechanism which allow storing data related to the view of the web applications. That includes all data in your controls which is not yet saved to database. But the problem is this can get quite large and take  big bite out of your bandwidth and performance.

When to Use View State & How it Work

Imagine that you have a control that allows you to edit a specific object (Lets say a very complex user information form like  a table). Every time you change/click something on your control, something about that object is changed. But only if you click save it should update things in your database. Where do you keep your data object in between changes until the last submit is received from save button?

You may use session variables, but then the data object is not local to your control/page. It’s there in the session. If user close the browser or navigate to another page at some point it is still in the session. With complex objects that takes up memory this can be problematic. Specially if you want your application to be scalable, you don’t want to put unnecessary things in to session. And you don’t want to keep things longer than you need in memory. How would you deal with this? Very often you will find yourself keeping track of things using hidden fields in HTML forms.

View State does exactly the same thing transparently to the programmer. In ASP.Net view state  is a method provided by framework to keep track of states of various ASP.Net controls across multiple requests. In a view state enabled ASP.Net control, you can store things in an indexed collection called ‘View’. All the information you put there will be available in server after next submit.

Ex:-

View["UserAccountObject"] = new UserAccount();  //Setting
UserAccount userAccountInfo = View["UserAccountObject"] //Getting

Only requirement is, whatever you put in to view must be serializable. Because under the hood it uses serialization to convert everything in ‘View’ to a Base 64 encoded string and store in a hidden field, so that it will be available in server after next submit. If you want to see the content of view state in your ASP.Net pages, view the rendered HTML code in your browser and look for a hidden field named __VIEWSTATE. You will not be able to read its content because of encoding, but it is not encrypted or compressed by default.

When View State Become a Big Ugly Monster

The problem is, in a complex ASP.Net control which has a lot of view data, this hidden filed will contain a very large BLOB of encoded data. It can even be multiple megabytes. All this adds to your page/payload size and affect download speed, response time and bandwidth usage of both visitors and server. Also, if you have things like HTTP compression set-up to compress HTML output, this view information will add more load on your compression library and CPU of both server and client, which will slow things down further. Sending view state back and forth through the connection is also a security risk, but there are mechanisms to encrypt it. Then again, it will add to delays and server load when a large chunk of view state need to be encrypted/decrypted.

However, instead of sending view state information to the client and make it send it back with the submit, you can easily keep it inside the server, either in server file system or in a database. Ofcourse some assembly is required.

Storing View State On Server

In every ASP.Net page a function named “SavePageStateToPersistenceMedium” is called when the view state need to be saved for later retrieval. And when it need to be retrieved another method named “LoadPageStateFromPersistenceMedium” is called.

You just need to override these two functions in your ASP.Net page class. However, doing this in each of your ASP.Net page class will be a very time consuming. Therefore the best approach is to write a class that extends System.Web.UI.Page class, which is the supper class for all the ASP.Net pages. Then you can use that new class as super class for your ASP.Net Pages.

Your new class will look like following…

using System;
using System.Web.UI;
using System.IO;

public class ViewStateOnServerPage : System.Web.UI.Page
{
    protected override void SavePageStateToPersistenceMedium(object viewState)
    {
        //Delegate responsibility of saving view state to View State manager
        base.SavePageStateToPersistenceMedium(ViewStateManager.SavePageStateToPersistenceMedium(viewState));
    }
    protected override object LoadPageStateFromPersistenceMedium()
    {
        //Delegate responsibility of reading view state to View State manager
        return ViewStateManager.LoadPageStateFromPersistenceMedium(base.LoadPageStateFromPersistenceMedium());
    }
}

There is no magic in above code. SavePagestateToPersistanceMedium function simply delegate the job to a similar function in ViewStateManager class. That function is responsible of storing the view state in whatever the medium and then return a ‘key’ which it can later use to retrieve the saved content. That returned key is passed in to SavePagestateToPersistanceMedium function is super class, which will ultimately store that key in __VIEWSTATE hidden field.

LoadPageStateFromPersistanceMedium function simply call the same function in base class to get tha ‘key’ which was created and stored in SavePagestateToPersistanceMedium function. Then that ‘key’ will be passed in to LoadPageStateFromPersistenceMedium function in ViewStateManager class which will load actual view state using the ‘key’.

Now lets take a look at a very simple ViewstateManager class

using System;
using System.Web.UI;
using System.IO;

public class ViewStateManager
{
    /* These two variables hold the location on disk
     * in which the view state will be saved.
     * In a production application you will want to have this values
     * taken from a configuration file
     * */
    static string file_path_prefix = @"c:\ViewStateCache\";
    static string file_path_sufix = ".viewstate";
    /*
     * LosFormatter is a serializer used for serialising
     * view state in ASP.Net. You can use any serializer.
     * But this one is designed to encode data faster without worrying about storage efficiency.
     * Therefore this generate a lot of data but it run faster.
     * */
    static LosFormatter los = new LosFormatter();

    /*
     * This function generates a file name to store view state
     * We are using a GUID generator to keep the file name unique
     * */
    private static string NewFileName()
    {
        return file_path_prefix + Guid.NewGuid().ToString() + file_path_sufix;
    }

    /*
     * This is the function which actually do the dirty work and save
     * view state in to disk
     * */
    public static object SavePageStateToPersistenceMedium(object viewState)
    {
        //Serialize the view state into a base-64 encoded string
        StringWriter writer = new StringWriter();
        los.Serialize(writer, viewState);
        //Save the string to disk
        String filePath = NewFileName();
        StreamWriter sw = new StreamWriter(File.Create(filePath));

        sw.Write(writer.ToString());
        sw.Close();

        /*
         * We will use file path itself as a key.
         * But this is not the safest thing to do
         * you need to either encrypt this or use another level of indirection
         * which support validation in a production environment
         * I'll leave that part for you to figure
         * */
        return filePath;
    }

    /*
     * This function load the view state from disk using the key generated in
     * Above function
     */
    public static object LoadPageStateFromPersistenceMedium(object key)
    {
        //Our key is the file path so we just need to open that file
        string filepath = key.ToString();

        if (!File.Exists(filepath))
            return null;
        else
        {
            // Open the file and read the content in to a string
            StreamReader sr = new StreamReader(File.Open(filepath, FileMode.Open));
            string viewStateString = sr.ReadToEnd();
            sr.Close();
            // De-serialize view state and return
            return los.Deserialize(viewStateString);
        }
    }
}

In above ViewStateManager class, SavePageStateToPersistenceMedium function first serialize incoming view state object in to a string using a LosFormatter instance which is kept as a static member of the class. Here you can actually use any serializer. But LosFormatter is specifically developed for View State serialization and has properties favorable to this scenario.

After serializing the content it just save it to a file in the disk using a StreamWriter. File name is generated using a helper function named ‘NewFileName’. It generates file names using a GUID generator to avoid conflicts.  In this function we have used file path itself as the key. Because if we have the file path at read end, that’s all we need to read the content.

‘LoadPageStateFromPersistenceMedium’ function simply load the file specified in the incoming ‘key’ and load it to a string. And then it deserialize the string using LosFormatter to get our original view state.

Now when you create a new ASP.Net page. All you need to do is to modify corresponding .cs file to change its super class to ‘ViewStateOnServerPage’ instead of System.Web.UI.Page and you are good to go. Using this approach you will be able to make your ASP.Net pages load faster in to the clients using less bandwidth in both client and server at the cost of disk space in server.  With some minor changes you can make a ViewStateManager which save data in a database instead of flat files.

Also if you don’t want to change all your pages to have this class as the super class there is a much cleaner way to make this take effect. That is to make your ViewStateOnServer class a subclass of PageAdapter  and set that as the default page adapter for all browsers. To do this create an XXX.browser file inside App_Browsers folder in your web app and add following content. See here for more on Control Adapters.

<browsers>
   <browser refID ="Default">
      <controlAdapters>
         <adapter
                controlType="System.Web.UI.Page"
                adapterType="<ViewStateONServer class name>" />
      </controlAdapters>
   </browser>
</browsers>

However you have to keep in mind that, what is given above is a very over simplified ViewStateManager. In a production environment you will want to have the values of ‘file_path_prefix’ and ‘file_path_sufix’ variables taken from Web.Config file. Also you will not want to send actual paths of view state file as the key. Because then you will have to open a file in the server, based on a file path sent by client. That is not safe. You have to at least encrypt the key with an encryption key unique to that session. It’s even better if you simply return a hash of the file or implement a mechanism to check if the file was created by your own view state manager before opening the file.

Also you will notice that above code creates a new view state cache file per each request. This is necessary. Because you can’t discard old view state just because you read it back once. User may decide to hit back button. User may be opening multiple pages in multiple browsers or tabs. As a result you might suddenly get a request to load up an old view state file. So you need to keep old files. But you will need to implement a mechanism to limit the number of view state files you keep per session. Otherwise you will eat up the disk space sooner than you think. You can easily do this by keeping a list of view state files per session in a bounded list. Whenever the list reach it’s maximum size, you delete the oldest file. You will also need to find a way to delete view state files of expired sessions. This could be done easily if you maintain a list like above in a session variable. Once the session is terminated, your list will be eligible for garbage collection and you can have your clean up code in the destructor.

Hope you find this useful!

/Rakhitha


Tags: , , , , , , , , , , , , , , , , , , , , , , , , ,

3 comments on “ASP.Net: Reduce Page Size, and Make Pages Load Faster by Storing View State on Server

  1. Rakhitha says:

    One more thing, in addition to above you can also wrap the file stream with a GZipStream to zip the content of view state. That way you can save some disk space. But there will be a slight performance cost.

    But if view state objects are large enough it can be worth it. Compression happen fast when it is in memory and reducing the amount of data that you have to read/write in disk will improve performance as a result of reduced Disk IO.

    /Rakhitha

  2. Karan says:

    This is the query regarding the class ViewStateOnServerPage. Inside the class you have overridden two methods

    1. SavePageStateToPersistenceMedium
    2. LoadPageStateFromPersistenceMedium

    In the former method you have written

    base.SavePageStateToPersistenceMedium(ViewStateManager.SavePageStateToPersistenceMedium(viewState));

    and in the later method you have written

    return
    ViewStateManager.LoadPageStateFromPersistenceMedium(base.LoadPageStateFromPersistenceMedium());

    Do you think both things are right

  3. Rakhitha says:

    Hi..!
    Yes they are correct. I am not sure what exactly you think is wrong. Is it about switching placess between ViewStateManager and base, yes, they should switch places.

    Because when you save you first save the view state to DB/file and then you send the key back to client so that you can load it later.

    When you load you need to first get the key from client and then only you can load the content from DB/File.

    Hope I answered your question
    /Rakhitha