October 28, 2005
Moving VIEWSTATE to the bottom of the page redux
Scott Hanselman and Jeff Atwood recently wrote about moving the ASP.NET hidden _VIEWSTATE form field to the end of the form. The theory behind this movement is that indexers such as Google only read the beginning of the page HTML or treat the beginning HTML with more respect than the rest of the page. If that is true, than it is a bad thing to have the viewstate, which is basically gobbletygook, taking up precious real estate near the top of the page. A side benefit to moving viewstate is that the page source is much easier to read if you don't have to scroll down past the big block of gibberish.
Scott shows how to move the viewstate by overriding the Render method of a base Page class. I believe this is the same approach used by the DotNetNuke folks. I show how to do the same thing with an HttpModule. The main advantage of using an HttpModule is that you can add this to your site without implementing a base Page class or even compiling anything, all you need is one of the assemblies attached below. There are some restrictions:
- This module must come before any compression module or any other module that changes the page HTML in such a way that the __VIEWSTATE field is hidden. Note that I had to disable the blowery.HttpCompression module that was running on this site to get the MoveViewState module to work. No matter what order the two modules were loaded, the content was always compressed before MoveViewState saw it. My guess is that ASP.NET calls the BeginRequest handlers in some specific order that I don't understand.
- This module assumes that the first </form> tag on the page corresponds to the first __VIEWSTATE field (normally true, but if you have somehow implemented multiple forms, it may not work for you).
- This module assumes UTF8 encoding will work to decode and encode your page contents. If you are using some other encoding, then the contents will not look like text and viewstate will not be moved [the page will still display correctly].
The code itself is really very simple. There are just two classes: MoveViewStateModule and MoveViewStateFilter. MoveViewStateModule implements IHttpModule. During IHttpModule.Init it adds an event handler to the HttpApplication.BeginRequest event. During the BeginRequest event, it creates a new MoveViewStateFilter and assigns it to the Response.Filter. Here's the code in both VB and C#:
MoveViewStateModule.vb
Public Class MoveViewStateModule
Implements System.Web.IHttpModule
Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
End Sub
Public Sub Init(ByVal context As System.Web.HttpApplication)
Implements System.Web.IHttpModule.Init
AddHandler context.BeginRequest, AddressOf BeginRequestHandler
End Sub
Private Sub BeginRequestHandler(ByVal sender As Object, ByVal e As EventArgs)
Dim application As System.Web.HttpApplication = CType(sender, System.Web.HttpApplication)
application.Response.Filter = New MoveViewStateFilter(application.Response.Filter)
End Sub
End Class
MoveViewStateModule.cs
public class MoveViewStateModule : System.Web.IHttpModule
{
public MoveViewStateModule() {}
void System.Web.IHttpModule.Dispose() {}
void System.Web.IHttpModule.Init(System.Web.HttpApplication context)
{
context.BeginRequest += new EventHandler(this.BeginRequestHandler);
}
void BeginRequestHandler(object sender, EventArgs e)
{
System.Web.HttpApplication application = (System.Web.HttpApplication) sender;
application.Response.Filter = new MoveViewStateFilter(application.Response.Filter);
}
}
Response.Filter is a System.IO.Stream. Whatever filter we assign must be chained to the previous filter. [And whater filter is assigned later will do the same thing]. The upstream filter will write to our MoveViewStateFilter and our MoveViewStateFilter must write to the downstream filter.
MoveViewStateFilter is implemented as a type of System.IO.MemoryStream. All it does is capture everything that is written to it in a buffer. When the Close method is called, the buffer is converted into a string. The string is searched for the hidden __VIEWSTATE form field. If the field is found, then the entire viewstate form field is moved to the end of the form. Here is the code in both VB and C#:
MoveViewStateFilter.vb
Public Class MoveViewStateFilter
Inherits System.IO.MemoryStream
Private _filter As System.IO.Stream
Private _filtered As Boolean = False
''' <param name="filter">A reference to the downstream HttpResponse.Filter.</param>
Public Sub New(ByVal filter As System.IO.Stream)
_filter = filter
End Sub
''' <remarks>
''' The contents of this filter are written to the downstream filter after the hidden
''' __VIEWSTATE form field is moved.
''' </remarks>
''' <summary>Closes this filter stream.</summary>
Public Overrides Sub Close()
If _filtered Then
If Me.Length > 0 Then
Dim bytes() As Byte
Dim content As String = System.Text.Encoding.UTF8.GetString(Me.ToArray)
Dim viewstateStart As Integer
viewstateStart = content.IndexOf("<input type=""hidden"" name=""__VIEWSTATE""")
If viewstateStart >= 0 Then
Dim viewstateEnd As Integer
viewstateEnd = content.IndexOf("/>", viewstateStart) + 2
Dim viewstate As String
viewstate = content.Substring(viewstateStart, viewstateEnd - viewstateStart)
content = content.Remove(viewstateStart, viewstateEnd - viewstateStart)
Dim formEndStart As Integer = content.IndexOf("</form>")
If formEndStart >= 0 Then
content = content.Insert(formEndStart, viewstate)
End If
bytes = System.Text.Encoding.UTF8.GetBytes(content)
Else
bytes = Me.ToArray
End If
_filter.Write(bytes, 0, bytes.Length)
End If
_filter.Close()
End If
MyBase.Close()
End Sub
Public Overrides Sub Write(ByVal buffer() As Byte, _
ByVal offset As Integer, ByVal count As Integer)
If Not System.Web.HttpContext.Current Is Nothing _
AndAlso System.Web.HttpContext.Current.Response.ContentType = "text/html" Then
MyBase.Write(buffer, offset, count)
_filtered = True
Else
_filter.Write(buffer, offset, count)
_filtered = False
End If
End Sub
End Class
MoveViewStateFilter.cs
using System;
namespace StructuredSolutions.MoveViewState
{
/// <summary>Moves the hidden __VIEWSTATE form field to the end of the form.</summary>
public class MoveViewStateFilter : System.IO.MemoryStream
{
System.IO.Stream _filter;
bool _filtered = false;
/// <param name="filter">A reference to the downstream HttpResponse.Filter.</param>
public MoveViewStateFilter(System.IO.Stream filter)
{
_filter = filter;
}
/// <summary>Closes this filter stream.</summary>
/// <remarks>
/// The contents of this filter are written to the downstream filter after the hidden
/// __VIEWSTATE form field is moved.
/// </remarks>
public override void Close()
{
if (_filtered)
{
if (this.Length > 0)
{
byte[] bytes;
string content = System.Text.Encoding.UTF8.GetString(this.ToArray());
int viewstateStart = content.IndexOf("<input type=\"hidden\" name=\"__VIEWSTATE\"");
if (viewstateStart >= 0)
{
int viewstateEnd = content.IndexOf("/>", viewstateStart) + 2;
string viewstate = content.Substring(viewstateStart, viewstateEnd - viewstateStart);
content = content.Remove(viewstateStart, viewstateEnd - viewstateStart);
int formEndStart = content.IndexOf("</form>");
if (formEndStart >= 0)
content = content.Insert(formEndStart, viewstate);
bytes = System.Text.Encoding.UTF8.GetBytes(content);
}
else
{
bytes = this.ToArray();
}
_filter.Write(bytes, 0, bytes.Length);
}
_filter.Close();
}
base.Close();
}
public override void Write(byte[] buffer, int offset, int count)
{
if ((System.Web.HttpContext.Current != null)
&& ("text/html" == System.Web.HttpContext.Current.Response.ContentType))
{
base.Write(buffer, offset, count);
_filtered = true;
}
else
{
_filter.Write(buffer, offset, count);
_filtered = false;
}
}
}
Once you have the module in hand, all you have to do is convince ASP.NET to load it. This is done by putting the assembly in the bin folder of the site and adding the module to the list of <httpModules> in web.config.
web.config
<system.web>
<httpModules>
<!-- To use the C# version, use this add instead of the other one
<add type="StructuredSolutions.MoveViewState.MoveViewStateModule, StructuredSolutions.MoveViewStateCS"
name="MoveViewStateModule" />
-->
<add type="StructuredSolutions.MoveViewState.MoveViewStateModule, StructuredSolutions.MoveViewStateVB"
name="MoveViewStateModule" />
</httpModules>
</system.web>
Assemblies
Copy either one of the assemblies from the zip file to the bin directory of your site. Make sure the <httpModules> section is loading the one you choose.
File Attachment: MoveViewState.zip (4 KB)
Project Files
File Attachment: MoveViewStateVB.zip (2 KB)
File Attachment: MoveViewStateCS.zip (2 KB)
Update October 22, 2005: Disable filter if content type is not text/html.
Update July 17, 2006: Fix insertion point and add search for meta tag to disable moving viewstate on a specific page:
<meta name="moveviewstate" content="nomove">
This site looks much better in a browser that supports current web standards, but it is accessible to any browser.
Download one now
Some parts of this site will not work effectively on this older browser.
Please consider
updating your browser