Blog Home  Home Feed your aggregator (RSS 2.0)  
Search Engine Optimization - Moving View State to the bottom of the page - Manuel Abadia's ASP.NET stuff
 
# Thursday, 04 December 2008

One of the greatest problems when trying to optimize an ASP.NET page to be more search engine friendly is the view state hidden field. Most search engines give more score to the content of the firsts thousands of bytes of the document so if your first 2 KB are view state junk your pages are penalized. So the goal here is to move the view state data as down as possible.

I have seen some approaches to solve this problem rewriting the final HTML code of the response. While this approach works I think that it wastes some precious processor cycles that can be used to do other things. So I needed a way to do the same thing without wasting that CPU time. After some large reflector sessions I found a way to do it. My method uses the ASP.NET Control Adapter Architecture.

A control adapter is a class that can be used to control the HTML generated by the control it adapts. Since the Page class is the responsible of rendering the view state hidden field (Page.BeginFormRender calls Page.RenderViewStateFields), an adapter for the Page is needed. However, the view state hidden field plays a key role in the ASP.NET infrastructure (for example, the Page.IsPostBack property checks if the view state hidden field has been posted) and it is difficult to modify the associated HTML.

A PageAdapter has a method called GetStatePersister() that returns an object that inherits from PageStatePersister. The PageStatePersister is called when it is time to load and save the view state. There are 2 classes that inherit from PageStatePersister: HiddenFieldPageStatePersister and SessionPageStatePersister. The first one is the default, which stores the view state in the hidden field called __VIEWSTATE. The second one stores the view state in the session. So, we can easily create a custom PageStatePersister to control the view state load and save process. The big problem is how to create the hidden view state field before the closing form tag while being a fully transparent solution. After some tries I came up with a solution that I was happy with.

I realized that it was impossible to completely remove the view state hidden field from the top of the page, because it plays a key role in the ASP.NET infrastructure. However, with any custom page state persister the ASP.NET infrastructure renders at least an empty view state hidden field of only 70 bytes:

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="" />

 

My page adapter adds a hidden field to the bottom of the form called __SEOVIEWSTATE with the actual view state data, and the only limitation that it has it is that you can not use <% %>expressions directly inside the asp.net form. However, this restriction can be easily avoided putting the <% %>expression in a PlaceHolder control or inside another control. For an in-depth explanation of this limitation take a look here.

Let’s see an example of the adapter in action. The following ASP.NET page:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="SEOViewStateAdapterTest._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        Enter a message: <br />
        <asp:TextBox ID="txtMessage" runat="server"></asp:TextBox><br />
        <asp:Button ID="bSaveMessage" runat="server" Text="Save Message" onclick="bSaveMessage_Click" /><br />
        <asp:Label ID="lMessage" runat="server"></asp:Label><br />
       
        <p>Page generated at <asp:PlaceHolder ID="PlaceHolder1" runat="server"><%= DateTime.Now.ToString("hh:mm dd/MM/yyy") %></asp:PlaceHolder></p>
    </div>
    </form>
</body>
</html>
 

 

With the associated code:

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace SEOViewStateAdapterTest
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack) {
                ViewState["previousMessage"] = txtMessage.Text;
            }
        }

        protected void bSaveMessage_Click(object sender, EventArgs e)
        {
            lMessage.Text = String.Format("The current message is '{0}'. The previous message was '{1}'", txtMessage.Text, (string)ViewState["previousMessage"]);
            ViewState["previousMessage"] = txtMessage.Text;
        }
    }
}
 

 

after a couple of postbacks, without using the adapter, the HTML looks like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>

</title></head>
<body>
    <form name="form1" method="post" action="Default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJc01l[...]VwP+cfdSWI6Q==" />
</div>

<div>

    <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAwKb4uenCgK/1s7/DwKf8MMPfiUvZtKPSXk//XdxkLooz8QDI0Y=" />
</div>
    <div>
        Enter a message: <br />
        <input name="txtMessage" type="text" value="Message 2" id="txtMessage" /><br />
        <input type="submit" name="bSaveMessage" value="Save Message" id="bSaveMessage" /><br />
        <span id="lMessage">The current message is 'Message 2'. The previous message was 'Message 1'</span><br />
       
        <p>Page generated at 08:13 04/12/2008</p>
    </div>
    </form>
</body>
</html>

 

and after a couple of postbacks, using the adapter, the HTML looks like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>

</title></head>
<body>
    <form name="form1" method="post" action="Default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="" />
</div>

<div>

    <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAwL+raDpAgK/1s7/DwKf8MMPyF7nqN1AbwNwFBq8OAjEAQorsyo=" />
</div>
    <div>
        Enter a message: <br />
        <input name="txtMessage" type="text" value="Message 2" id="txtMessage" /><br />
        <input type="submit" name="bSaveMessage" value="Save Message" id="bSaveMessage" /><br />
        <span id="lMessage">The current message is 'Message 2'. The previous message was 'Message 1'</span><br />
       
        <p>Page generated at 08:11 04/12/2008</p>
    </div>
    <input type="hidden" name="__SEOVIEWSTATE" id="__SEOVIEWSTATE" value="/wEPc01lc3[...]CdNY6AtgigHvU=" /></form>
</body>
</html>

 

In order to use the adapter, you have to add a reference to the assembly and add a file called SEOViewStateAdapter.browser (the name of the file does not matter. The extension needs to be the same. Or also you could merge the contents to another file if you already have one) to the App_Browsers folder. The content of the file should be:

<browsers>
    <!-- use the adapters for all browsers -->
    <browser refID="Default">
        <controlAdapters>
            <!-- ths adapter is used to save the view and control state at the bottom of the form so the page is more friendly to search engines -->
            <adapter controlType="System.Web.UI.Page" adapterType="Manu.Web.Adapters.SEOFriendlyViewStatePageAdapter, SEOViewStateAdapter" />
        </controlAdapters>
    </browser>
</browsers>
 

 

That’s all. Enjoy!

SEOViewState.zip (29.67 KB)
Thursday, 04 December 2008 20:26:16 (Romance Standard Time, UTC+01:00)  #    Comments [11]   ASP.NET | Microsoft .NET Framework | SEO  | 
Monday, 05 January 2009 14:19:18 (Romance Standard Time, UTC+01:00)
With ASP.NET 3.5 SP1, you can just add 'renderAllHiddenFieldsAtTopOfForm="false"' to the <pages> directive in your web.config.
Monday, 05 January 2009 15:43:09 (Romance Standard Time, UTC+01:00)
Timothy, yes but there is very few people that actually have ASP.NET 3.5 SP1 in production.
Monday, 05 January 2009 18:44:10 (Romance Standard Time, UTC+01:00)
Yes, this is much "easier" in ASP.NET 3.5 SP1. Here's a blog posting on that: http://blog.jonathangill.org/jonathan_gill/2008/12/how-to-speed-up-aspnet-page-loading-by-moving-the-viewstate-to-the-bottom-of-the-page.html
Monday, 05 January 2009 21:16:57 (Romance Standard Time, UTC+01:00)
I don't see any meta tags in your example.
Dan
Tuesday, 06 January 2009 11:19:50 (Romance Standard Time, UTC+01:00)
Dan,

the example was about improving SEO by moving the view state to the bottom of the page. It wasn't my intention to show everything you should do to improve SEO in ASP.NET.
Tuesday, 06 January 2009 15:09:34 (Romance Standard Time, UTC+01:00)
The best optimization is still:

... pages enableViewState="false" ...
Tuesday, 06 January 2009 22:12:49 (Romance Standard Time, UTC+01:00)
'renderAllHiddenFieldsAtTopOfForm="false"' has absolutely NO effect on the placement of ViewState.

See the following blog post:

http://leedumond.com/blog/moving-asp-net-hidden-fields-to-the-bottom-the-easy-way/

It will move the other hidden fields, like (__EVENTVALIDATION, __EVENTTARGET, __EVENTARGUMENT, and so on, but not ViewState.

Try it for yourself and see.
Thursday, 08 January 2009 18:57:25 (Romance Standard Time, UTC+01:00)
Congratulations! You have been added to the Articles list in DefaultDotAspx.com.

http://www.DefaultDotAspx.com/Articles.aspx
Thursday, 23 April 2009 06:25:24 (Romance Daylight Time, UTC+02:00)
Hi,
This is really a good post!.SEO is really getting bigger.The more technical it also gets.
Tuesday, 28 April 2009 23:00:07 (Romance Daylight Time, UTC+02:00)
great article

thanks
Tuesday, 19 May 2009 05:28:53 (Romance Daylight Time, UTC+02:00)
Great work, nice article but couldn't you just use SpiderLoop and save all the work?
Skech
All comments require the approval of the site owner before being displayed.
Name
E-mail
Home page

Comment (Some html is allowed: a@href@title, strike) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

[Captcha]Enter the code shown (prevents robots):

Live Comment Preview
Copyright © 2017 Manuel Abadia. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.