There are two reasons why it is desirable to do so. The first is for letting search engines see more of your content rather than the big portion of ViewState many sites have. The other is perceived rendering time, which means that the content loads faster because it renders before the ViewState while the total rendering time remains the same. That will decrease the load time of your website’s content.

Techniques to move the ViewState to the bottom of the WebForm has been published many times before. What I wanted was adding the functionality to an HttpModule. The technique to move the ViewState is borrowed from Scott Hanselman while the HttpModule implementation is my own. As Scott writes, it is a very low impact technique (0.000995 second) even though it hasn’t been fully tested for a variety of scenarios.

The goal I’m trying to achieve is to build a reusable component that has 100% plug ‘n play capabilities. That’s where the HttpModule comes in. You can just drop it into any existing website without changing any code.

I see no reasons why not to move the ViewState to the bottom, which makes me believe that Microsoft should have done that by default in the first place.

Implementation

Download the ViewstateModule.cs below and put in the App_Code folder of your website. Then add these lines to the web.config and you’re ready to go.

<httpModules>

  <add type="ViewstateModule" name="ViewstateModule" />

</httpModules>

Download

ViewstateModule.zip (1,06 KB)

Comments

 NinjaCross

Thanks for posting, but unfortunately the module doesn't work correctly. The viewstate is removed, but never restored on the bottom cause the following condition (line 123) is never true. if (formEndStart &gt;= 0) { html = html.Insert(formEndStart, viewstateInput); } The test application is so basic, and no other http modules are hosted (so no interferencies are expected). I didn't look deeply in the code, so i can't figure out the motivation. If I've got a little spare time, I'll try to investigate :)

NinjaCross

Mads Kristensen

That's strange. It works perfectly correct for me, even on a basic application. The only way the statement can never be true is because you don't have any form on your web page or the form-tag is not lowercased. If that's not the case, then I'm a little curios to hear what you find out from your investigation. I'll try to debug and see if I can come up with something. I think it is unlikely I find something wrong because it has worked for me on different websites in the past.

Mads Kristensen

 NinjaCross

There is a form, and it's tag is lower-cased. Maybe other factors are concurring in the problem... I'll look at the code a little bit deeply and I'll let you know

NinjaCross

Mads Kristensen

Thanks. I look forward to hearing about your findings.

Mads Kristensen

 NinjaCross

Ok, I found a first hint. I noticed that the whole html stream is not send in a unique call to "Write(byte[] buffer, int offset, int count)". It's instead splitted into many chunks and sent to the method with sequential calls. The problem is in the fact that on the first call if (startPoint &gt;= 0) is true, but in the current chunk there isn't the form closing tag cause it's in the second (or Nth) call of the method Write. When in the Nth call of Write the buffer contains the form closing tag, the condition if (startPoint &gt;= 0) is not true anymore. A solution could be to extract if (formEndStart &gt;= 0) from the block of if (startPoint &gt;= 0) and handling them separately, but this would imply conflicts in the case of a page with multiple forms if an appropriate check is not provided too. A second solution would be to force (in some way) ASP.NET to send to Write the whole stream, instead of splitting it into many calls. I'll leave to you the honour to find the most correct way to solve this problem ;)

NinjaCross

Mads Kristensen

That might be because you have set Buffer="false" on either the page or in the web.config.

Mads Kristensen

 NinjaCross

I already checked this... and the Buffer attribute is not specified in the page, neither in the web.config or in the machine.config. Maybe it's value is optional also in the machine.config (I didn't check the schema declaration, so I can only suppose...) and the default value make the module behave in this way. Unfortunately, if this is the real cause of the problem, I think that the module should be redesigned in some ways to make it more robust. Infact I dont' think it would be recommendable to use it if it's prone to global configuration aspects (think i.e. to deploy your applications on cutomers' servers where you can't decide how to apply this kind of politics) Anyway, thanks again for posting :)

NinjaCross

Mads Kristensen

The Buffer attribute is True by default and it makes no sense to change it on the machine.config level because it is by nature an application setting. I still don't understand why you cannot make it work when it has been working perfectly for me on several projects.

Mads Kristensen

 Adel K. Khalil

i just wanted to thank you so much, simple yet great idea.

Adel K. Khalil

Ingmar Hoogendoorn

Nice! I think it's better to change the source a bit. If (on whatever reason) your code can't find the &lt;/form&gt; tag, the viewstate will be removed and not inserted. So it's better to change the code to: int endPoint = html.IndexOf("/&gt;", startPoint) + 2; int viewstateLength = endPoint - startPoint; string viewstateInput = html.Substring(startPoint, viewstateLength); int formEndStart = html.IndexOf("&lt;/form&gt;") - 1; if (formEndStart &gt;= 0) { html = html.Remove(startPoint, viewstateLength); html = html.Insert(formEndStart - viewstateLength, viewstateInput); } }

Ingmar Hoogendoorn

 Paul Kinlan

I seem to remember that the reason why viewstate is at the top of the page, is because if the page posts back before it finishes rendering at least the internal state of the page on the server can restore it's state correctly. If you have the viewstate at the end of the page and it hasn't finished rendering (i.e the viewstate has not been rendered) before the screen posts back then you are up a certain creek without a paddle... For that reason alone I would not recommend anyone use this method.

Paul Kinlan

Ingmar Hoogendoorn

If a page load is taking so long, there is something wrong. I don't think it's a problem, 'normal' pages are being pushed to the server in no time and before a user clicks something, their will be even more time. Remember ASP.NET renders the page before it's getting submit to the client, so this shouldn't be a problem at all. And it does help for search engine optimization.

Ingmar Hoogendoorn

 NinjaCross

Uhm... I think you are not considering applications that imply large interactive reports. Sometimes my xhtml reports has got a weight of 800-900KB, and I can ensure you that the loading+rendering time is not exactly something that I would call "short" ;) In that case, a module like this (even if so interesting and usefull for many other cases) could be the source of big headeaches. Obviously, everything has got its own application field, so also the usage of this module should be reasoned

NinjaCross

 Paul Kinlan

"If a page load is taking so long, there is something wrong" Well not really, 3 seconds is not considered un-normal and whilst the page may have rendered with the viewstate not the users might have already clicked the link because they knew where it would be. As long as you can stop them from submitting requests back to the server, you are okay otherwise you could easily have the situation I have described, for instance if someone has a slow internet connection.

Paul Kinlan

 David Neal

Very cool! However, I found that in my case: int formEndStart = html.IndexOf("&lt;/form&gt;") - 1; should actually be: int formEndStart = html.IndexOf("&lt;/form&gt;"); If there happens to be no space between the last HTML tag and &lt;/form&gt; then it was inserting the viewstate before the closing "&gt;" of the previous HTML tag.

David Neal

 Sachman Bhatti

Like David Neal I also found that same problem. It would produce an extra "&gt;" and not validate.

Sachman Bhatti

 Dotnetshadow

Hi Guys, I seem to have a problem with this code when I use a compression module, I've tried to make sure one module loads before the other but still the same problem. Has anyone tried this with a compression module? Regards DotnetShadow

Dotnetshadow

 Martin Meixger

Hi, thanks for this nice piece of code. Moving the Viewstate by overriding Render() didnt work for me, because it breaks the asp wizard control behavior. I used Mad's code and tweak it - as David Neal and NinjaCross already stated before. Now it seems to work flawless. Please let me know, if this works for you also. here my tweaked code based on Mad's work: http://www22.brinkster.com/gvspm/viewstatemodule.zip Regards Martin Meixger

Martin Meixger

 Michael Cole

Modified the code a little: 1. moved viewstateInput variable outside the write function 2. moved the check for closing form tag outside the check for viewstate startpoint if statement 3. removed the "-1" after the check for closing form tag Renders perfectly just where it needs to be ... at the bottom of the page. code: string viewstateInput; public override void Write(byte[] buffer, int offset, int count) { byte[] data = new byte[count]; Buffer.BlockCopy(buffer, offset, data, 0, count); string html = System.Text.Encoding.Default.GetString(buffer); int startPoint = html.IndexOf(_viewstate tag here); if (startPoint &gt;= 0) { int endPoint = html.IndexOf("/&gt;", startPoint) + 2; viewstateInput = html.Substring(startPoint, endPoint - startPoint); html = html.Remove(startPoint, endPoint - startPoint); } int formEndStart = html.IndexOf(form tag here); if (formEndStart &gt;= 0) { html = html.Insert(formEndStart, viewstateInput + "\n"); } byte[] outdata = System.Text.Encoding.Default.GetBytes(html); _sink.Write(outdata, 0, outdata.GetLength(0)); } End Code Thanks for the original code. Been trying to figure out how to use httpModules with no complete example until I stumbled upon this blog. Cheers, Michael Cole

Michael Cole

accessdenied

What if I have 2 "form" tags in my code? For example when I added Google Search to my page. This code inserts viewstate before first occurrence of "/form".

accessdenied

Free images for webmasters

If you have 2 (or more) "form" tags on a page, you have to change int formEndStart = html.IndexOf on int formEndStart = html.LastIndexOf

Free images for webmasters

DotNetKicks.com

Trackback from DotNetKicks.com An HttpModule that moves ViewState to the bottom of the Page

DotNetKicks.com

Korayem.NET

It's worth noting that this breaks ASP.AJAX behavior by returning errors: "object reference not set to an object".

Korayem.NET

Ted Jardine

Also note that if you're using an asp:Substitution control (for dynamic data in a page with PageOutput caching), this method does not work. Took me a bit to figure that out. I'm sure there's a way around it, but the fact that problems can arise if there's a postback prior to full page render (as mentioned above), I'll just keep using a base page that removes all (well, most) traces of ViewState for the pages I don't need it on, and the very few I do, I'll just live with it as is.

Ted Jardine

[soksa]icy

Perfect example. Thanks a milllion Ingmar Hoogendoorn. And of course Michael Cole, for saving me the trouble of correcting the code. It's so funny. I never read the comments, just copy &amp; paste the code/module, run into all the troubles posted here, solve them, then come and try to post the solution only to find out that somebody had already been there, done that :)) ...When everything else fails, read the documents!!! LOL Kudos Icy

[soksa]icy

[soksa]icy

Here's a weird case I've run into today. The viewstate was sooo long that it was cut in half on the first loop of Write(). So I had to modify the routine a little bit: /************* begin code ********************/ string viewstateInput; bool isCompleted = false; public override void Write(byte[] buffer, int offset, int count) { byte[] data = new byte[count]; Buffer.BlockCopy(buffer, offset, data, 0, count); string html = System.Text.Encoding.Default.GetString(buffer); int endPoint; int startPoint = html.IndexOf("&lt;input type=\"hidden\" name=\"__VIEWSTATE\""); if (startPoint == -1) startPoint = 0; if (!isCompleted) { endPoint = html.IndexOf("/&gt;", startPoint) + 2; if (endPoint &lt; startPoint &amp;&amp; !isCompleted) { //this is probably the first loop and the viewstate has started somewhere in the buffered output, //but not completed on this run. so grab whatever there is after the start point to the end endPoint = html.Length; viewstateInput = html.Substring(startPoint, endPoint - startPoint); } else { //viewstate end was captured viewstateInput = html.Substring(startPoint, endPoint - startPoint); isCompleted = true; } //remove whatever was captured from the buffer html = html.Remove(startPoint, endPoint - startPoint); } int formEndStart = html.IndexOf("&lt;/form&gt;"); if (formEndStart &gt;= 0) { html = html.Insert(formEndStart, viewstateInput + "\n"); } byte[] outdata = System.Text.Encoding.Default.GetBytes(html); _sink.Write(outdata, 0, outdata.GetLength(0)); } /************* end code *******************/ But there's a bug here: What if the viewstate string was cut right after "/", before the "&gt;" point? We capture the beginning of the viewstate input in a precise manner, but the closing of the tag is captured with the assumption that the buffered string will contain "/&gt;" chars somewhere either after the capture point, or in the above example somwhere after the beginning of the string. What if the first buffer ended with "/" only, and the second buffer started with "&gt;"? The first loop will catch the beginning, and trim the remainder of the output, the second loop will know that the viewstate was cut in half and that it has to finish the rest - but it can never know where to stop, since the "/&gt;" won't be there, and will capture a bunch of code from the HTML output itself 'til the first encounter of "/&gt;". Himpf, this one's a bit tricky, any ideas? :) Cheers Icy

[soksa]icy

huobazi

I think do it in a PageAdapter is better then in HttpModule and my code is private string MoveViewState( string html ) { int startPos = 0; startPos = html.IndexOf( "&lt;input type=\"hidden\" name=\"__VIEWSTATE\"" ); if ( startPos &gt;= 0 ) { int endPos = html.IndexOf( "/&gt;" , startPos ) + 2; string viewStateInput = html.Substring( startPos , endPos - startPos ); html = html.Remove( startPos , endPos - startPos ); int formEndPos = html.IndexOf( "&lt;/form&gt;" , startPos ); if ( formEndPos &gt;= 0 ) { html = html.Insert( formEndPos , viewStateInput ); } } return html; } The Adapter Pattern can do many thing in asp.net such as move/rewrite the form action view the current page of your blog(dotnetblogengine) you can find some code like &lt;form name="aspnetForm" method="post" action="../post.aspx?id=bf674c52-a761-4886-9cf7-e25532491660" it is very ugly and display the real url not the rawurl in the browser address bar. so you can do it in a HtmlFormAdapter my code //------------------------------------------------------------------------------ // &lt;Disclaimer&gt; // Author:Huobazi http://www.AspxBoy.com &amp;&amp; http://huobazi.aspxboy.com // &lt;/Disclaimer&gt; //------------------------------------------------------------------------------ using System; using System.Collections.Generic; using System.Text; using System.Web; using System.Web.UI; using System.Reflection; using System.IO; namespace AspxBoy.DotNetJobs.Adapters { public class HtmlFormAdapter : BaseAdapter { protected override void Render( System.Web.UI.HtmlTextWriter writer ) { base.Render( new FormRewriteTextWriter( writer ) ); } } internal class FormRewriteTextWriter : HtmlTextWriter { private static readonly string alreadyRewirteKey = "AspxBoy.DotNetJobsCn.FormActionAlreadyRewrote"; public FormRewriteTextWriter( TextWriter writer ) : base( writer ) { if ( writer is HtmlTextWriter ) { this.InnerWriter = ( writer as HtmlTextWriter ).InnerWriter; } else { this.InnerWriter = writer; } } public override void WriteAttribute( string name , string value , bool fEncode ) { HttpContext context = HttpContext.Current; if ( name == "action" &amp;&amp; context.Items[ alreadyRewirteKey ] == null ) { value = context.Request.RawUrl; context.Items[ alreadyRewirteKey ] = true; } base.WriteAttribute( name , value , fEncode ); } } } By the way : there is no 'China' in the Country dropdownlist when i want to add a comment, why???

huobazi

Paraesthesia

Trackback from Paraesthesia Mock a Page Request Lifecycle with TypeMock

Paraesthesia

Jonah

This might be, or not, important for some... If you're using FreeTextBox with the Viewstatemodule it can create undefined errors. I often got "object reference not set to an instance of an object" when trying to save data from the FreeTextBox. It drove me nuts until I realized that the Viewstatemodule was to blame.

Jonah

BrandSpankingNewbie

Dotnetshadow (and Mads) - I have the same problem: when I try to use the compression module found here: along with the 'move the viewstate' module on this page, the viewstate won't budge - it will render at the top of the output html. If I comment out the compression module, the viewstate module works again - I'd love any input/direction/ideas that anyone has on this - thanks a bunch!

BrandSpankingNewbie

BrandSpankingNewbie

Sorry, forgot to include the link of the page with the compression module that I'm using (also from Mads): http://blog.madskristensen.dk/post/HTTP-compression-of-WebResourceaxd-and-pages-in-ASPNET.aspx

BrandSpankingNewbie

Robi

The code doesn't work if the viewstate is too large. I get this error: "Length cannot be less than zero. Parameter name: length."

Robi

Tim Brandt

Icy good work around. As for the bug with end symbols: /&gt; being split, the work around isn't that difficult. Just make sure to incorporate additional logic where you continuously inspect your viewStateInput string. If the last char is / then you know to look for &gt;. Two things to note: 1. Potentially the first check for the viewstate itself could be split. The dirty fix would be to add some html padding to make certain that isn't happening. 2. Likewise the &lt;/form&gt; could be getting split, once again some padding could resolve the issue. Personally I would rather create a class that inherits off of page and override the render method and move the viewstate to the bottom myself. The only problem with that is the legacy site that we are working on has some ineritance going on that we don't want to much with. PS I realize my post come a year late. meNoCode

Tim Brandt

busby seo test

wow! nice post, thanks for sharing ideas, I really appreciate it.

busby seo test

anandh

Thanks , We modified your function and it's working perfectly thanks public override void Write(byte[] buffer, int offset, int count) { byte[] data = new byte[count]; Buffer.BlockCopy(buffer, offset, data, 0, count); string html = System.Text.Encoding.Default.GetString(buffer); int startPoint = html.IndexOf("&lt;input type=\"hidden\" name=\"__VIEWSTATE\""); if (startPoint &gt;= 0) { int endPoint = html.IndexOf("/&gt;", startPoint)+2; if (endPoint &lt;= 0) { endPoint = html.Length; } viewstateInput = html.Substring(startPoint, endPoint - startPoint); html = html.Remove(startPoint, endPoint - startPoint); } int formEndStart = html.ToUpper().IndexOf("&lt;/FORM&gt;") - 1; int eNdPoint = html.IndexOf("&lt;/form&gt;"); if (formEndStart &gt;= 0) { html = html.Insert(formEndStart, viewstateInput); } byte[] outdata = System.Text.Encoding.Default.GetBytes(html); _sink.Write(outdata, 0, outdata.GetLength(0)); }

anandh

Comments are closed