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.
Introduction
A common scenario in web development is when one page shows different content depending on some query parameters. OK, but these query parameters do not look friendly to the user and are neither friendly for the search engines. Here the URL Rewriting comes. We have URL rewriting in SilverlightShow.net sitemap page. Look for example here:
http://www.silverlightshow.net/Sitemap-Articles.aspx
You see that the articles are organized in pages and when clicking on “Older Articles” or “Newer Articles” the page is changed and the URL looks slightly different. Only the number at the end of the page name is different, for example “Sitemap-Articles-1.aspx” or “Sitemap-Articles-2.aspx”.
The problem
It’s needless to say that all these different URLs are processed by a single ASP.NET page. The problem came when we decided to put an ASP.NET SiteMapPath control. By default it automatically binds using the information provided in the Web.sitemap file. OK, we have the following SiteMap structure:
<?xmlversion="1.0"encoding="utf-8" ?>
<siteMapxmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNodeurl="~/Default.aspx"title="Home" description="Home">
<siteMapNodeurl="~/Sitemap.aspx"title="Sitemap"description="Sitemap">
<siteMapNodeurl="~/Sitemap-News.aspx"title="News"description="News"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Articles.aspx"title="Articles" description="Articles">
<siteMapNodeurl="~/Sitemap-Announcements.aspx"title="Announcements"description="Announcements"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Products.aspx"title="Products"description="Products">
<siteMapNodeurl="~/Sitemap-Controls.aspx"title="Controls"description="Controls"></siteMapNode>
<siteMapNodeurl="~/Sitemap-MediaPlayers.aspx"title="Media Players"description="Media Players"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Games.aspx"title="Games"description="Games"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Charts.aspx"title="Charts"description="Charts"></siteMapNode>
</siteMapNode>
<siteMapNodeurl="~/Sitemap-Tools.aspx"title="Tools"description="Tools"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Moonlight.aspx"title="Moonlight"description="Moonlight"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Demos.aspx"title="Demos"description="Demos"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Learn.aspx"title="Learn"description="Learn">
<siteMapNodeurl="~/Sitemap-Videos.aspx"title="Videos"description="Videos"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Presentations.aspx"title="Presentations"description="Presentations"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Tutorials.aspx"title="Tutorials"description="Tutorials"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Resources.aspx"title="Resources"description="Resources"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Samples.aspx"title="Samples"description="Samples"></siteMapNode>
<siteMapNodeurl="~/Sitemap-QuickStarts.aspx"title="QuickStarts"description="QuickStarts"></siteMapNode>
<siteMapNodeurl="~/Sitemap-TipsandTricks.aspx"title="Tips and Tricks"description="Tips and Tricks"></siteMapNode>
</siteMapNode>
<siteMapNodeurl="~/Sitemap-Issues.aspx"title="Issues"description="Issues"></siteMapNode>
<siteMapNodeurl="~/Sitemap-Misc.aspx"title="Misc"description="Misc"></siteMapNode>
</siteMapNode>
<siteMapNodeurl="~/Sitemap-Shows.aspx"title="Shows"description="Shows"></siteMapNode>
</siteMapNode>
</siteMapNode>
</siteMap>
We have the root website node, marked with title “Home” and the sitemap node marked with title “Sitemap”. All child nodes of the “Sitemap” node except the last child (Shows) are processed by a single ASP.NET page. Since the URL is different no matter that all these nodes are in fact one ASP.NET page the SiteMapPath works the way it has to work. For example if we are on the following URL:
http://www.silverlightshow.net/Sitemap-Articles.aspx
Our SiteMapPath is rendered the following way:

If you look at our Web.sitemap file you will see that it works fine. Till now everything is OK, but imagine that we want to open the last page in the Articles page, which is on the following URL:
http://www.silverlightshow.net/Sitemap-Articles-1.aspx
It works on SilverlightShow.net because we have made it work but in common scenario it wouldn’t work. No SiteMapPath would be shown because in the Web.sitemap we don’t have a node with url="~/Sitemap-Articles-1.aspx". You can always say that we can add this node too but in our case lots of articles are added to the site every day and the pages become more and more. I don’t like the option of checking for new pages every day and updating the Web.sitemap file.
The solution
The solution is to attach to the SiteMap.SiteMapResolve in the OnLoad handler of the page that processes the request where paging is needed.
We have the following code in the code behind of our page:
protected override void OnLoad( EventArgs e )
{
base.OnLoad( e );
SiteMap.SiteMapResolve += new SiteMapResolveEventHandler( this.ConstructSiteMap );
Note: You can do the same in the Page_Load method if your ASP.NET page is marked with: AutoEventWireup="true". I’m overriding the OnLoad event handler because it is faster :)
Note: SiteMap class is located in the System.Web namespace and is an in-memory representation of the navigation structure of the website. It is used by the SiteMapPath control.
Here is the handler for the SiteMapResolve event.
private SiteMapNode ConstructSiteMap( Object sender, SiteMapResolveEventArgs e )
{
SiteMapNode currentNode;
if ( SiteMap.CurrentNode == null )
{
currentNode = e.Provider.FindSiteMapNode( "~/Sitemap-Articles.aspx " );
}
else
{
currentNode = SiteMap.CurrentNode.Clone( true );
}
return currentNode;
}
In our case if the URL is with paging it is not found in the Web.sitemap file and that’s why the SiteMap.CurrentNode property will be null. This causes the SiteMapPath control to disappear. OK, now we check if this property of the SiteMap is null and if it is we just create a SiteMapNode using the current provider’s SiteMapProvider.FindSiteMapNode method giving the URL without paging. Note that we surely will know the URL that must be given to the FindSiteMapNode method because we are in the page which processes the request. If this property is not null we just clone it. The return value of our handler will be the SiteMapNode which will be used to render the SiteMapPath control.
This is a better solution than updating the Web.sitemap file every day.
Additional Issues and solutions
I found out that the SiteMap class has a strange way of caching. For example if we go to: http://www.silverlightshow.net/Sitemap-Controls.aspx our SiteMapPath will look like this:

After that we are going to http://www.silverlightshow.net/Sitemap-Articles.aspx and our SiteMapPath is normal:

But the problem comes when we go through the pages. If we hadn’t find a solution our SiteMapPath path would look this way, no matter that we are on the Articles page and not on the Controls page:

This behavior is as strange as our solution. It seems that the SiteMap remembered that when last time the CurrentNode was null we made it “~/Sitemap-Controls.aspx” and when we are on Articles page and this node is again null it uses the previous value from Controls. As I told you our solution is also strange but works :) We are putting our page path in variable instead of literal:
currentNode = e.Provider.FindSiteMapNode( this.currentPagePath );
Our variable of type string currentPagePath contains for example the following value “~/Sitemap-Games.aspx” for the following page: http://www.silverlightshow.net/Sitemap-Games.aspx. The behavior is exactly the same. But if we make this variable null just after our page is unloaded no caching takes place:
protected override void OnUnload( EventArgs e )
{
base.OnUnload( e );
this.currentPagePath = null;
}
Note: In our case our Controls and Articles pages are processed by a single ASP.NET page. But the caching behavior of the SiteMap takes place even these two URLs are processed by different ASP.NET pages. Our solution works for our Shows page which is different ASP.NET page. You just must make the variable which contains the current node path null on both places.