| Microsoft | Articles | Forums | FAQs |
| C# .NET |  |  |  |  |
| VB.NET |  |  |  |  |
| Visual Studio .NET |  |  |  |  |
| ADO.NET |  |  |  |  |
| Xml / Xslt |  |  |  |  |
| VB 6.0 |  |  |  |  |
| .NET CF |  |  |  |  |
| GDI+ |  |  |  |  |
| LINQ |  |  |  |  |
| Deployment |  |  |  |  |
| Security |  |  |  |  |
| FoxPro |  |  |  |  |
| Silverlight / WPF |  |  |  |  |
| Entity Framework |  |  |  |  |
| RIA Services |  |  |  |  |
|
| Web Programming | Articles | Forums | FAQs |
| JavaScript |  |  |  |  |
| ASP |  |  |  |  |
| ASP.NET |  |  |  |  |
| Web Services |  |  |  |  |
|
| Non-Microsoft | Articles | Forums | FAQs |
| NHibernate |  |  |  |  |
| Perl |  |  |  |  |
| PHP |  |  |  |  |
| Ruby |  |  |  |  |
| Java |  |  |  |  |
| Linux / Unix |  |  |  |  |
| Apple |  |  |  |  |
| Open Source |  |  |  |  |
|
| Databases | Articles | Forums | FAQs |
| SQL Server |  |  |  |  |
| Access |  |  |  |  |
| Oracle |  |  |  |  |
| MySQL |  |  |  |  |
| Other Databases |  |  |  |  |
|
| Office | Articles | Forums | FAQs |
| Excel |  |  |  |  |
| Word |  |  |  |  |
| Powerpoint |  |  |  |  |
| Outlook |  |  |  |  |
| Publisher |  |  |  |  |
| Money |  |  |  |  |
|
| Operating Systems | Articles | Forums | FAQs |
| Windows 7 |  |  |  |  |
| Windows Server |  |  |  |  |
| Windows Vista |  |  |  |  |
| Windows XP |  |  |  |  |
| Windows Update |  |  |  |  |
| MAC |  |  |  |  |
| Linux / UNIX |  |  |  |  |
|
| Server Platforms | Articles | Forums | FAQs |
 |  |  |  |  |
| BizTalk |  |  |  |  |
| Site Server |  |  |  |  |
| Exhange Server |  |  |  |  |
| IIS |  |  |  |  |
|
| Graphic Design | Articles | Forums | FAQs |
| Macromedia Flash |  |  |  |  |
| Adobe PhotoShop |  |  |  |  |
| Expression Blend |  |  |  |  |
| Expression Design |  |  |  |  |
| Expression Web |  |  |  |  |
|
| Other | Articles | Forums | FAQs |
| Subversion / CVS |  |  |  |  |
| Ask Dr. Dotnetsky |  |  |  |  |
| Active Directory |  |  |  |  |
| Networking |  |  |  |  |
| Uninstall Virus |  |  |  |  |
| Job Openings |  |  |  |  |
| Product Reviews |  |  |  |  |
| Search Engines |  |  |  |  |
| Resumes |  |  |  |  |
|
| |
|
|
|
Keep ViewState out of Page for Performance Enhancement Redux By Peter Bromberg Printer Friendly Version View My Articles 36 Views |  |
New features in ASP.NET 2.0 enable developers to get Viewstate out of the Page and on to the server for up to 500% better throughput. |
ASP.NET 2.0 now provides built in support for PageStateAdapters, which utilize a page adapter and the new SessionPageStatePersister class. You can also write your own Adapter class. You can find examples by other developers typically storing ViewState in files on the server, although this really doesn't make much sense to me since storing a judicious amount of Viewstate in memory (Session or Cache) is orders of magnitude faster.
The cool thing about ASP.NET 2.0 is that Scott Guthrie and his merry band have either providerized or adapterized just about everything, making the developer's job of "doing stuff" humongously easier.
The SessionPageStatePersister class (when decompiled) looks like so:
[AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal)]
public class SessionPageStatePersister : PageStatePersister
{
// Fields
private const string _viewStateQueueKey = "__VIEWSTATEQUEUE";
private const string _viewStateSessionKey = "__SESSIONVIEWSTATE";
// Methods
public SessionPageStatePersister(Page page) : base(page)
{
HttpSessionState session = null;
try
{
session = page.Session;
}
catch
{
}
if (session == null)
{
throw new ArgumentException(SR.GetString("SessionPageStatePersister_SessionMustBeEnabled"));
}
}
public override void Load()
{
if (base.Page.RequestValueCollection != null)
{
try
{
string requestViewStateString = base.Page.RequestViewStateString;
string second = null;
bool flag = false;
if (!string.IsNullOrEmpty(requestViewStateString))
{
Pair pair = (Pair) Util.DeserializeWithAssert(base.StateFormatter, requestViewStateString);
if ((bool) pair.First)
{
second = (string) pair.Second;
flag = true;
}
else
{
Pair pair2 = (Pair) pair.Second;
second = (string) pair2.First;
base.ControlState = pair2.Second;
}
}
if (second != null)
{
object obj2 = base.Page.Session["__SESSIONVIEWSTATE" + second];
if (flag)
{
Pair pair3 = obj2 as Pair;
if (pair3 != null)
{
base.ViewState = pair3.First;
base.ControlState = pair3.Second;
}
}
else
{
base.ViewState = obj2;
}
}
}
catch (Exception exception)
{
HttpException e = new HttpException(SR.GetString("Invalid_ControlState"), exception);
e.SetFormatter(new UseLastUnhandledErrorFormatter(e));
throw e;
}
}
}
public override void Save()
{
bool x = false;
object y = null;
Triplet viewState = base.ViewState as Triplet;
if ((base.ControlState != null) || ((((viewState == null) || (viewState.Second != null)) || (viewState.Third != null)) && (base.ViewState != null)))
{
HttpSessionState session = base.Page.Session;
string str = Convert.ToString(DateTime.Now.Ticks, 0x10);
object obj3 = null;
x = base.Page.Request.Browser.RequiresControlStateInSession;
if (x)
{
obj3 = new Pair(base.ViewState, base.ControlState);
y = str;
}
else
{
obj3 = base.ViewState;
y = new Pair(str, base.ControlState);
}
string str2 = "__SESSIONVIEWSTATE" + str;
session[str2] = obj3;
Queue queue = session["__VIEWSTATEQUEUE"] as Queue;
if (queue == null)
{
queue = new Queue();
session["__VIEWSTATEQUEUE"] = queue;
}
queue.Enqueue(str2);
SessionPageStateSection sessionPageState = RuntimeConfig.GetConfig(base.Page.Request.Context).SessionPageState;
int count = queue.Count;
if (((sessionPageState != null) && (count > sessionPageState.HistorySize)) || ((sessionPageState == null) && (count > 9)))
{
string name = (string) queue.Dequeue();
session.Remove(name);
}
}
if (y != null)
{
base.Page.ClientState = Util.SerializeWithAssert(base.StateFormatter, new Pair(x, y));
}
}
}
So, for example, if I wanted to create a CachePageStatePersister, I would model it after the above class, derive from PageStatePersister base, and substitute Cache for Session where appropriate. We could just as well have added in compression here, although I wonder how much of an improvement the additional CPU churning might get us.
I've also seen a number of articles and blog posts recently where developers are attempting to hijack the ViewState hidden field and its contents and add it back in at the bottom of the Page, just before the closing </FORM> tag. This is done ostensibly for SEO reasons as the GoogleBot and other crawlers supposedly only read the first "XX" bytes of the page and if it is glommed up with ViewState, the page isn't indexed properly. There is little point in doing this in my opinion, because you may have made your page "SEO Friendly", but you may still have a huge StateBag of ViewState making the round trip with the page, only now it is in a different position on the page! Fawlty logic, methinks...
One of the biggest performance killers in ASP.NET applications is ViewState. Period! If you doubt this, please read Tess Ferrandez excellent post here, subtitled "Death by ViewState". She's nailed the problem cold, she just doesn't offer all the solutions.
The very first thing we want to do of course is disable ViewState on the page unless it is absolutely necessary. Failing that, the following solution will go a long way towards not only making the page SEO-Friendly, but eliminating that glop of ViewState by storing it on the server.
In a previous article, http://www.eggheadcafe.com/articles/20040613.asp , I did considerable testing on this and determined that storing ViewState in Cache was the most efficient. Session was a bit slower, but it obviated the need to create a unique key for each instance. The new built - in SessionPageStatePersister class automatically accounts for nine (9) pages "back" of ViewState due to users pressing the "Back" button on their browser. So, in exchange for sacrificing a small amount of throughput due to falling back to Session storage of ViewState instead of the slightly faster Cache storage, we get a lot of new built-in functionality without having to custom-code anything. To me, that's a pretty good trade-off.
The first step is to create a Page Adapter that returns an instance of the built-in SessionPageStatePersister class (instead of the default HiddenFieldPageStatePersister class which stores viewstate in a hidden field on the client, as we all well know).
First, create a class library that contains the following code. The easiest way to do this (especially for debugging and testing) is to simply add a new "class library" project to your solution:
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
namespace PAB.Web
{
public class PageStateAdapter : System.Web.UI.Adapters.PageAdapter
{
public override PageStatePersister GetStatePersister()
{
return new SessionPageStatePersister(this.Page);
}
}
}
That's all it takes. But, what about customization? For example, one one site I have a custom 404.aspx page that is used to handle extensionless UrlRewriting. Unfortunately, I cannot have session state involved on this one page as it interferes. So, I can customize the StatePersister:
public override PageStatePersister GetStatePersister() { if (HttpContext.Current.Request.RawUrl.IndexOf("404.aspx") > -1) { return new HiddenFieldPageStatePersister(this.Page); } else { return new SessionPageStatePersister(this.Page); } }
Next, create a new "Default.browser" special browser config file that specifies that your new page adapter should be used for all browsers:
<browsers> <browser refID="Default"> <controlAdapters> <adapter controlType="System.Web.UI.Page" adapterType="PAB.Web.PageStateAdapter" /> </controlAdapters> </browser> </browsers>
Add a new ASP.NET "App_Browsers" Folder to your web app, using the built-in context menu option in Visual Studio 2005, and then add your new Default.browser file into the folder (and bring it into your project from within Visual Studio Solution Explorer).
You will need to copy your new App_Browsers folder containing your Default.browser Browser file to the root of your web application on your server,along with a copy of your (in my case) PAB.Web.dll assembly from your new project into the /bin folder on the server.
Now, all requests that come in from any type of browser will sink using your new adapter that returns a SessionPageStatePersister instance. You can verify that this is working by viewing a page on your site, then use trace.axd and view the session information; you should see that it contains the viewstate info entries from the decompiled class example above. Viewing the client page source will also show that the majority of the viewstate is no longer stored on the client; there will still be a nominal amount (about 200 bytes) of Viewstate present in the __VIEWSTATE field.
This is the same Adapter configuration that allows us to do many other things such as CSS-friendly control adapters and much more -- well worth some additional study.
I have this running on two separate sites now, and the improvements in throughput are so noticeable, there is really no need for any speed testing. Not only that, but I've cut my bandwidth consumption considerably, which makes the sites more profitable since I don't have to pay for extra bandwidth from the hosting company. |
 |
| Biography - Peter Bromberg |
Peter Bromberg is a C# MVP, MCP, and .NET expert who has worked in banking, financial and telephony for over 20 years. Pete focuses exclusively on the .NET Platform, and currently develops SOA and other .NET applications for a Fortune 500 clientele. Peter enjoys producing digital photo collage with Maya,playing jazz flute, the beach, and fine wines. You can view Peter's UnBlog and IttyUrl sites. Please post questions at forums, not via email! |  |
|
|
 |
| Article Discussion: Keep ViewState out of Page for Performance Enhancement Redux |
| Peter Bromberg posted at Monday, October 22, 2007 8:44 PM |
 | Original Article |
 |
| |
|
| Performance versus scalability |
| Michael Urquiola replied to Peter Bromberg at Wednesday, October 24, 2007 10:46 PM |
| I would be concerned that storing that much additional data in memory will hurt scalability. I agree that initial performance will likely be better. Caching is one of those trade offs that generally is good for single user performance but bad for scalability unless it is done very judiciously. In performance work I've done we've made great improvements by getting everything not used system wide out of cache and streaming out just the data we need on demand. |
 |
| |
|
| The article uses Session to store ViewState, not Cache. |
| Peter Bromberg replied to Michael Urquiola at Wednesday, November 21, 2007 5:39 PM |
 | It also talks about reducing or even eliminating ViewState as the first step one should take. Memory on the WebServer is cheap, and extremely fast. I don't understand why Caching would supposedly be "good for single user performance" since it is application-wide and works for all users. But again, the example code (unless you want to go back to the previously mentioned article) does not use Cache at all. If you are using an animal such as a GridView control, which spews out horrid amounts of ViewState into the page, you have not got the choice of "streaming out just the data we need on demand" -- GridView will lose much functionality -- such a Paging / Sorting -- if ViewState is disabled. |
 |
| |
|
| Session is a type of cache |
| Michael Urquiola replied to Peter Bromberg at Wednesday, November 21, 2007 9:29 PM |
When you hold objects in the middle tier in memory, that is caching. The only point I was trying to make is this lets say for arguments sake that view state is 500kb for a particlar view. For one user it is much faster to put this session/cache on the server than to stream it back and forth to the client, if you make it 100 or 1000 users you're now talking about a serious amount of memory for all those concurrent users. If you instead stream it out memory is not an issue. One of the first PerfMon stats I look at when doing load testing is %TimeInGC, and the only way to bring down that number is to reduce the amount of memory you are allocating and keeping alive in the middle tier. Saying that memory is cheap misses the point entirely, a particular 32 bit application can allocate 2gb of memory, an ASP.Net 1.1 app will start running into Out of Memory exceptions at 800mb, how expensive it is doesn't matter if there is not enough of it to go around.
As to the GridView, I agree with you. Which is why we wrote our own that does not use viewstate and does all its sorting filtering client side and only retrieves data not html from the server. |
 |
| |
|
| Some Performance problevs of using SessionPageStatePersister |
| Ermakov Alex replied to Peter Bromberg at Friday, November 28, 2008 7:45 AM |
| Hello, Peter. Thanks for your article. It was so much helpfull for me. But I'd like to write about problems I've encountered.
There are some pages in my project that has realy big viewstate and that uses Ajax. It took realy a lot of time for Ajax javascript to work with viewstate and I've desided to take away viewstate from the page using this article. You can't imagine how surprised was I when I've looked in profiler and saw that most time of the request takes for the SessionStateModule.OnReleaseState method. As I can understand - my viewstate was too big, so session could not handle it. So i've rewrited code:
using System;
using System.Web.Caching;
using System.Web.SessionState;
using System.Web.UI;
using System.Collections;
namespace TestWebServ.Site.Classes
{
public class CachePageStatePersister : PageStatePersister
{
private Cache cache = null;
private HttpSessionState session = null;
public CachePageStatePersister(Page page) : base(page)
{
try
{
session = page.Session;
cache = page.Cache;
}
catch
{
throw new Exception("ViewState не может быть инициализирован");
}
if (session == null)
{
throw new ArgumentException("Включите сессию, чтобы заработал вьюстэйт");
}
}
public override void Load()
{
string Key = Page.Request["_ViewStateKey"];
if(!string.IsNullOrEmpty(Key))
{
Key = (string)StateFormatter.Deserialize(Key);
if(cache[Key]== null)
{
throw new InvalidOperationException("Кэш не содержит такого ключа ViewState");
}
Pair statePair = (Pair)cache[Key];
ViewState = statePair.First;
ControlState = statePair.Second;
}
}
public override void Save()
{
if (ViewState != null || ControlState != null)
{
Pair statePair = new Pair(ViewState, ControlState);
string ticksString = Convert.ToString(DateTime.Now.Ticks, 0x10);
string Key = string.Concat(sessKey, ticksString);
cache.Insert(Key, statePair, null, DateTime.Now.AddMinutes(session.Timeout), Cache.NoSlidingExpiration,
CacheItemPriority.NotRemovable, null);
Queue queue = session["__VIEWSTATEQUEUE"] as Queue;
if (queue == null)
{
queue = new Queue();
session["__VIEWSTATEQUEUE"] = queue;
}
queue.Enqueue(Key);
if (queue.Count > 9)
{
cache.Remove((string) queue.Dequeue());
}
ScriptManager.RegisterHiddenField(Page, "_ViewStateKey", StateFormatter.Serialize(Key));
}
}
private string sessKey
{
get { return string.Concat("ViewStateSessionKey_", session.SessionID, "_"); }
}
}
}
As you can see it's uses cache to store viewstate itself. Now it's works very very fast.
One more thing, everebody who uses this class or SessionPageStatePersister should know. If there is showModal scripts on your page, or frames, you can have problems. For example if you open 10 pages via showModal from the page, then viewstate from this other page will be gone. Same for frames. If you are yousing frames or showModal - don't use SessionPageStatePersister or CachePageStatePersister on pages you are open in them. |
 |
| |
|
| Sorry for no formatting |
| Ermakov Alex replied to Ermakov Alex at Friday, November 28, 2008 7:47 AM |
| It must be becouse of Opera browser :( |
 |
| |
|
| What am I doing wrong? |
| Malay Thakershi replied to Ermakov Alex at Monday, March 30, 2009 4:50 PM |
| Hello,
I tried your previous solution (viewStateSvrMgr.cs class) but my issue got worse.
- I have a gridview and when I checked, viewstate was hitting about 30 kb.
- I implemented your solution and I think it is storing it in cache.
- But when I look at fiddler, I see there is a huge delay introduced (nothing happens between 3 and 15 seconds). Earlier, first to last request was completing in total 7 seconds.
Could you please help me figure out does your solution apply to my kind of issue? |
 |
| |
|
|
|