Moving ViewState to the bottom of the page
We noticed that the ViewState in silverlightshow.net was enormous so we decided to make some improvements. The first step was to move it to the bottom of the page just for the search engine optimization. I have met many articles in internet which describe how to do this. One of them which I have used is:
http://www.hanselman.com/blog/MovingViewStateToTheBottomOfThePage.aspx
I’ll describe the solution which worked for us:
I created a class named SLSPage which extends the System.Web.UI.Page class and made all ASP.NET pages inherit this class instead of the Page class. Here our class is:
using System.Text.RegularExpressions;
using System.IO;
using System.Web.UI;
using System;
using System.Configuration;
namespace SilverlightShow
{
public class SLSPage : System.Web.UI.Page
{
#region Constants
private static readonly Regex viewStateRegex = new Regex( @"<input\s+type=""hidden""\s+name=""__VIEWSTATE""\s+id=""__VIEWSTATE""\s+value=""[^""]+""\s*/>", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled );
private static readonly Regex endFormRegex = new Regex( @"</form>", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled );
#endregion
#region Handlers
protected override void Render( System.Web.UI.HtmlTextWriter writer )
{
string originalHtml = string.Empty;
string newHtml = string.Empty;
using ( StringWriter stringWriter = new StringWriter() )
{
using ( HtmlTextWriter htmlWriter = new HtmlTextWriter( stringWriter ) )
{
base.Render( htmlWriter );
originalHtml = stringWriter.ToString();
}
}
bool success = false;
Match viewStateMatch = viewStateRegex.Match( originalHtml );
if ( viewStateMatch.Success )
{
string viewStateString = viewStateMatch.Value;
newHtml = originalHtml.Remove( viewStateMatch.Index, viewStateMatch.Length );
Match endFormMatch = endFormRegex.Match( newHtml, viewStateMatch.Index );
if ( endFormMatch.Success )
{
newHtml = newHtml.Insert( endFormMatch.Index, viewStateString );
success = true;
}
}
if ( success )
{
writer.Write( newHtml );
}
else
{
writer.Write( originalHtml );
}
}
#endregion
}
}
We have two constant, precompiled Regex objects. The first matches the hidden field named __VIEWSTATE and the second – the closing tag of the form. Note that our solution will work only if we have only one form – the server form, but it is common scenario in ASP.NET.
#region Constants
private static readonly Regex viewStateRegex = new Regex( @"<input\s+type=""hidden""\s+name=""__VIEWSTATE""\s+id=""__VIEWSTATE""\s+value=""[^""]+""\s*/>", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled );
private static readonly Regex endFormRegex = new Regex( @"</form>", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled );
#endregion
Then I override the Render method where we have full control on the rendered html.
In this method I declare two string variables – one for the original rendered html and one for the html in which the ViewState will be at the bottom.
string originalHtml = string.Empty;
string newHtml = string.Empty;
I use the Render method of the Page class in order to render the html into a HtmlTextWriter , named htmlWriter, which wraps a StringWriter named stringWriter. Then I use the ToString method of the StringWriter in order to load the rendered html in the originalHtml variable.
using ( StringWriter stringWriter = new StringWriter() )
{
using ( HtmlTextWriter htmlWriter = new HtmlTextWriter( stringWriter ) )
{
base.Render( htmlWriter );
originalHtml = stringWriter.ToString();
}
}
Then I declare a Boolean variable called success, which by default is false:
This variable will be set to true only if all operations for moving the ViewState succeed. The following code moves the ViewState:
Match viewStateMatch = viewStateRegex.Match( originalHtml );
if ( viewStateMatch.Success )
{
string viewStateString = viewStateMatch.Value;
newHtml = originalHtml.Remove( viewStateMatch.Index, viewStateMatch.Length );
Match endFormMatch = endFormRegex.Match( newHtml, viewStateMatch.Index );
if ( endFormMatch.Success )
{
newHtml = newHtml.Insert( endFormMatch.Index, viewStateString );
success = true;
}
}
If the first match succeeds I remove the ViewState, save it in a temp variable named viewStateString, save the changed html in the newHtml variable and continue with the second match. If the second match succeeds I insert the viewstate tag just before the closing form tag and set the success variable to true. So only if all operations succeed we can use the modified html, otherwise we will use the original. I make a check:
if ( success )
{
writer.Write( newHtml );
}
else
{
writer.Write( originalHtml );
}
I use the HtmlTextWriter named writer, which we accept as a parameter of the Render method. This method writes the html to the output.
There are several occasions when some of the matches will not succeed and the original html must be rendered. For example when we use Ajax controls where we have partial rendering. In this situation only a part of the page html is rendered and there is no ViewState.
The second step was to optimize the ViewState so now its size is reduced up to 95%. In fact we do not need any more to move it to the bottom because only a small part of it remained. So this solution was just temporary. But if you don’t have enough time to make optimizations you can use this solution.