ViewState, even though it makes a developers life simple by enabling a rich even model, it is considered a performance evil. Performance evil not only because it adds a large amount of data in the Request & Response of a page, but also because you have very less control over it. This is until ASP.NET 4.0 came with a feature to disable then on a control-by-control basis, however it is not easy to just go into an existing application and disable ViewState for a control without changing behavior.
There are scenarios where you want to reduce the ViewState size when they grow big causing performance problems mainly because of the size of the page and post back. The solution I was thinking is to store the ViewState on the Server itself, probably in a Database when the page is being rendered and then retrieve it from the database when the post back happens before it is needed.
I looked around on the internet but didn’t find anything that would satisfy what I was thinking except an article on CodeProject. However there was no good implementation for ASP.NET 2.0 where we have a PageStatePersister.

In ASP.NET 1.0 there are two methods in the Page class that we can override to achieve this functionality

protected virtual void SavePageStateToPersistenceMedium (object viewState);
protected virtual object LoadPageStateFromPersistenceMedium();

The technique here would be to create an intermediate class that derives from Page class and overrides the two methods to do the manipulation. Then all your pages derive from that class instead of the Page class.

However in ASP.NET 2.0 onwards, Microsoft thankfully gave a much cleaner way to implement this solution. A good article about this is provided on Microsoft’s website (here). They provided with a base (abstract) class called PageStatePersister which is used by the ASP.NET Engine to save ViewStates and Adapter Model (PageAdapter) which decides which PageStatePersister class to instantiate. So in simple words it means if we implement our own PageStatePersister which saves ViewState in a database and change PageAdapter to return our Persister and not the default one (ie. HiddenFieldPageStatePersister)  the solution can be achieved.

Lets create a new ASP.NET Web Project or add these classes to an existing project you have to get this to work.

Now lets see how we can change the PageAdapter to switch our PageStatePersister.

  1. Extend PageAdapter to a Custom implementation, we will call it CustomPageAdapter .
  2. Override the method GetStatePersister() to return our PageStatePersister.
// CustomPageAdapter.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.Adapters;

namespace Sample
{
    public class CustomPageAdapter : PageAdapter
    {
        public override System.Web.UI.PageStatePersister GetStatePersister()
        {
            //return new CustomPageStatePersister(Page);
            return base.GetStatePersister();
        }
    }
}

Note: Line number 13 and 14 is what we will switch when we have a Custom Page State Persister created. We have not yet implemented the Persister.

Now to ensure that the CustomPageAdapter is called by the ASP.NET Engine and not the default .NET provided class:

  1. Add the ‘App_Browsers’ folder in the root of your web application.
  2. Use ‘Add > New Item…’ on the context menu of the folder to Create a Browser File, name it ‘Default.browser’.
Change the Default.browser file to add a ‘browser’ entry which extends ‘Default’.
<!-- /App_Browsers/Default.browser -->
<browsers>
-    <browser refID ="Default">
 		<controlAdapters>
			<adapter
				controlType="System.Web.UI.Page"
				adapterType="Sample.CustomPageAdapter" />
		</controlAdapters>
	</browser>
</browsers>

Now if you put a breakpoint on line 14 of CustomPageAdapter.cs you will see it is being called by the ASP.NET Engine. However at this point of time it simply passes the call to base method which returns an instance of the default ‘HiddenFieldPageStatePersister’ class until we write our own extention.
Now before we go any further lets make a few design decisions:

  1. Where do we want the actual ViewState data to be stored?
  2. What goes onto the page instead of the ViewState?
  3. Should we have capability to include/exclude specific pages incase problem is only in specific pages of the application?
After a detailed discussion with my team members we decided we will use a SQL Server table to store the ViewState on the server side with a GUID key, however this may affect performance in an adverse way. Saving & Retrieving from SQL Server could be reliable but an expensive operation. To improve performance we added a In Memory Caching Layer to it which means when saving the data it would save to SQL Server and a Cache while retrieving it will take it from the Cache and if the Cache is empty it would retrieve this from the Database. The trade off really is the time to save ViewState to Database is much lower than time for it to download and upload onto a user’s browser. You must evaluate the right decision here based on how bad is the ViewState size problem on your application.
We additionally wanted to keep an ON/OFF switch in case we want to completely Switch off this custom functionality in production.

So we ended up with a Configuration Key in web.config called ‘CustomPageStatePersister’

<!-- Web.config under the <appSettings> tag. -->
<add key="CustomPageStatePersister" value="off sql cache custom"/>

The various values of the CustomPageStatePersister key is space/comma separated. Essentially presence of a keyword in the value switches on/off a functionality.
Keyword  - Functionality
Off – Completely switches off the Custom behaviour of PageStatePersister, in this case it will call the default functionality as if the custom classes don’t exist.
Sql – Saves/Retrieves the actual ViewState to/from a SQL Server Table.
Cache –  Enables use of caching, so ViewState will also be saved in a cache and while retrieving the cached will be checked first.
Custom –  This is incase we want to selectively switch on and off page-by-page the behavior of the custom page persister. This would be done by implementing specific Empty Interfaces on pages.

Now lets create the class that will handle the Configuration we will call it
CustomPageStatePersisterConfiguration.

// CustomPageStatePersisterConfiguration.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;

namespace Sample
{
    public class CustomPageStatePersisterConfiguration
    {

        private const string CONFIGURATION_KEY = "CustomPageStatePersister";
        private const string CONFIGURATION_OFF = "off";
        private const string CONFIGURATION_SQL = "sql";
        private const string CONFIGURATION_CACHE = "cache";
        private const string CONFIGURATION_CUSTOM = "custom";

        public bool IsSwitchOff {get;set;}
        public bool IsCompressed { get; set; }
        public bool IsSqlPersisted { get; set; }
        public bool IsCached { get; set; }
        public bool IsOnCustomPageOnly { get; set; }

        public CustomPageStatePersisterConfiguration()
        {
            IsSwitchOff = true;
            IsCompressed = false;
            IsSqlPersisted =false;
            IsCached = false;
            IsOnCustomPageOnly = false;

            string configString = ConfigurationManager.AppSettings[CONFIGURATION_KEY];
            if (!string.IsNullOrEmpty(configString))
            {
                configString = configString.ToLower();

                if (!configString.Contains(CONFIGURATION_OFF))
                    IsSwitchOff = false;

                if (configString.Contains(CONFIGURATION_SQL))
                    IsSqlPersisted = true;

                if (configString.Contains(CONFIGURATION_CACHE))
                    IsCached = true;

                if (configString.Contains(CONFIGURATION_CUSTOM))
                    IsOnCustomPageOnly = true;

            }
        }

    }
}

I also decided to create a data encapsulation class just to keep things cleaner and not have multiple variables. we will call it the ‘CustomViewState’ entity, its a simple POCO class.

// CustomViewState.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Sample
{
    public class CustomViewState
    {
        public string Id { get; set; }
        public string Data { get; set; }
        public DateTime TimeStamp { get; set; }
    }
}

We also create two empty Interfaces to signal the Persister to do its job or not on a particular page.
If the Configuration key has “Custom” and ICustomStatePersistedPage interface is implemented on page,
the CustomPageStatePersister will kick in.
Regardless of the Configuration key if IDefaultStatePersistedPage interface is implemented on a page,
the CustomPageStatePersister will NOT kick in and will pass the calls to default HiddenFieldPageStatePersister.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;

namespace Sample
{
public interface ICustomStatePersistedPage
{}

public interface IDefaultStatePersistedPage
{}
}

Lets continue this in a second part of this post.

Reducing Size of ViewState in ASP.NET WebForms by writing a Custom ViewState Provider (PageStatePersister). Part 2/2

 

 

 

 

 

 

One Response to “Reducing Size of ViewState in ASP.NET WebForms by writing a Custom ViewState Provider (PageStatePersister). Part 1/2”

  1. Pretty nice post I must say,clear and concise.
    We also tried doing Viewstate caching on IIS 7 itself and gave out pretty good results.

Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

© 2013 Ashish Nangla's Blog Suffusion theme by Sayontan Sinha