As I announced in a previous post there is something to say about storing the view state on the server…
ASP.NET saves transient control data across postbacks in the “view state”. The means for persisting that data is a hidden field in the pages HTML code. Take some complex controls — especially grids that save the complete data they are working on in view state — the amount of view state data can easily reach, even exceed, the size of the remaining HTML. This can severely affect response times, not to mention the processing time on the server to de/serialize the data and to protect against tampering and other view state attacks.
Apart from enabling HTTP compression (which you should do anyway) and spending some thought on whether you need that particular view state data at all (not a bad idea either) the obvious solution for these issues is not to send the view state data over the wire at all. Let’s take a closer look at that option.
In ASP.NET 1.1. the page class supports customization of view state persistence with two methods:
- void Page.SavePageStateToPersistenceMedium(object viewState) (see MSDN)
- object Page.LoadPageStateFromPersistenceMedium() (see MSDN)
This has to be done for every page, but I would recommend using a common base class for all pages anyway.
The default implementation of SavePageStateToPersistenceMedium simply stores the view state in a member variable, actually storing it is defered until Page.OnFormRender is called. OnFormRender uses a LosFormatter to serialize the data into a string representation and puts it into a hidden field. LoadPageStateFromPersistenceMedium uses a LosFormatter to do the reverse. The role of that LosFormatter is noteworthy; the documentation states:
The limited object serialization (LOS) formatter is designed for highly compact ASCII format serialization. This class supports serializing any object graph, but is optimized for those containing strings, arrays, and hash tables. It offers second order optimization for many of the .NET Framework primitive types.
One should pay special attention to the fact that LosFormatter can deal with objects that are not marked as seriablizable, and thus cannot be handled by the standard .NET serialization.
If you want to take the view state data out of the hidden field you can do this simply by overriding those two methods. The things to take into account include:
- In order to properly support the browser history you need to track the particular page request and link it to the view state data. E.g. you may still use a hidden field to store some kind of view state ID.
- You must decide how much view state packages you maintain on the server. It’s not feasible to store all view states as long as the user session is alive. Store just one and the second “browser back” in a row will fail.
- You need some persistence medium to store the view state. The obvious choice would be the user session. However, since your session should be held out-of-proc (i.e. managed by the ASP.NET State Service) you cannot simply put the view state object into the state — see the serialization problem mentioned above — but you may use a LosFormatter and put that string in the session.
As a sidenote: Other articles about this can be found on the internet. They usually propose the ASP.NET cache or a file based approach.
The cache suffers from the same problems that in-proc session state has (see here). It also may remove cache entries far earlier than suitable for view state (e.g. under load conditions).
The file based approach will need some additional cleanup mechanism but has the best potential to support view state with a limitless browser history. It also consumes the least amount of memory.
The session approach suffers from the fact that all session content (triggered by and including all maintained view state packages) has to be serialized with every request. Cleanup however is done automatically.
If I had to think about a fully-fledged high-throughput solution, I would probably try out a combination of database (with a cleanup job) and caching.
I’m not going to provide code, since the algorithm can easily be taken form ASP.NET 2.0 and be applied to ASP.NET 1.1 (i.e. attached to SavePageStateToPersistenceMedium and LoadPageStateFromPersistenceMedium). The good news is that this implementation will work as well if you move to ASP.NET 2.0.
With ASP.NET 2.0 there is no change in principle, just some additional concepts. Make that a huge additional concept. And pittfalls.
Fact one: ASP.NET 2.0 introduced the concept of adapters which are registered in ”.browser”-files for bowser types. Adapters can interfere with different control aspects, such as rendering — and view state.
Fact two: The Page class now has a virtual property PageStatePersister. The default implementation of the ASP.NET 1.1 methods mentioned above now delegate the work to the instance returned by this property. And while HiddenFieldPageStatePersister still is the default persister, one may also return SessionPageStatePersister. Or a custom class.
Fact three: The documented way to replace the persister is to a) write an adapter and b) register it in a .browser file. And c) I don’t like this. First, .browser files do a lot more than just replacing the adapter. Second, how should anybody realize how this happens (the guy who has to maintain my code after I left). Third, in what way is this replacement browser specific? And anyway, I really don’t care for browser differences.
But why not simply forget about the “documented way” and just overwrite the property?
protected override PageStatePersister PageStatePersister
if (_pageStatePersister == null)
_pageStatePersister = new SessionPageStatePersister(this);
Great, just a few lines of code and I can throw away all the work I did for ASP.NET 1.1 … . ? What’s that? A serialisation exception? Oh boy, I know that problem. OK, I’ll derive my own persister … let’s see… persisters have a method CreateStateFormatter … which is internal? OK, … the formatter isn’t used anyway? The view state is simply put into the session state? And the method doing this is … well, of course it’s not virtual!
So, this leaves two options:
- If you already have a homegrown APS.NET 1.1 algorithm, just leave it as it is. It works, so why bother.
- If you start something new, try “copy and mend”, i.e. use your favourite help tool (Reflector!) to get the code from SessionPageStatePersister and fix the problems (just use a LosFormatter before putting the view state in the session). Don’t bother with virtual methods, you are the user of a framework, not the implementor.
And this can easily be ported back to ASP.NET 1.1 if the need arises.
So, finally there. And not too much work either. However this little experience has again raised the question whether (ASP).NET is meant to be extended and tailored to one’s needs — or just to be used as-is.