Introduction

This is a framework for caching serializable objects in general. It has the Cache generic class that caches any object in memory/disk. If it is no more in memory, it is read from disk.

It also has a CacheDictionary that does the same, but has some optimization for small data, avoiding many "buffer files" to be generated.

And, finally, the best of all: Adding the App_Browsers in your project, you can make all viewstates use this technology, so only a small ViewId is sent to the client instead of the full viewstate. Also, it deletes unused files after some time and reutilizes identical files, avoiding wasting HD space.

Background

I didn't really face many problems when working with the web. I started to program when computers had only 1MB or 2MB. But, I see that people in general are a bit lost on when to store the data. Documentation on ViewState and Sessions is also confusing, they look interchangeable, when they are not.

I first created a caching technology for paginating recordsets.

Then, I created the cache technology for any data.

And, finally, the module to do it for the ViewStates. And it is really great, and working for over a year without problems.

How It Works (Basic)

The implementation can be very complex, as the code is thread-safe and you need to manage garbage collection properly, but the principle is very simple:

Every object "buffer" is serialized and a hashcode (checksum) of that buffer is created. If a directory for that buffer exists, I look if there is some buffer with the same length and, if there is, if it is identical, so avoiding to create a new one, or I create a new unique id, save the file and return the id and hashcode of that buffer. This is shared among all sessions, but as the data is read-only, there is no problem.

The item 1 is the BufferId, not the ViewId. Now, with the bufferId, I try to find if a ViewState information in memory for the actual session which contains that bufferId. If none contains, a new ViewId is generated. If one exists, the Id of the old one is returned. Obviously, every time I reutilize a file, I do a keep-alive (in file, updating the date/time of the file, and in memory, calling GCUtils.KeepAlive).

Every buffer is also kept in memory with weak-references, but to avoid recent buffers to be collected, I call GCUtils.KeepAlive. The GCUtils.KeepAlive (not GC.KeepAlive) guarantees that an object will survive the next collection.

The ViewIds are created for the sessionId, so there is no problem of one user getting the viewstate of the other, even if the internal buffer is the same (in which case, every user will have different ViewIds, but they will point to the same buffer). Also, every page generates a NEW viewstate, and so I try to get the Id of an existing viewstate if possible, or I create a new file.

By default, FileCachePersister runs a process at 30 minutes, deleting files with more than 4 hours. This has nothing to do with session-expiration times.

How It Works (Advanced)

At this moment, I will not explain the details of the CacheDictionary (which is effectively a dictionary of Cache objects, but with some optimizations) and also not explain the WeakDictionary, because its explanation itself would be bigger than this entire article. But the important thing is to know that it is a dictionary that allows its items to be collected by the garbage collector, but keeps its recently used values alive.I will start explaining the CacheManager. The CacheManager class is responsible for loading and saving the buffers (bytes), as it does not know anything of the real type of the object. The most important thing is that it has is a WeakDictionary where the keys are the HashCodes of the buffers, and the values are Dictionaries of the Ids and the serialized bytes. Its internal functions try to find a value in memory using the hashcode and the buffer id and, if none is not found, ask for the persister to load it and then store the loaded value (if any) in these weak-dictionaries, doing a KeepAlive on them. The Save function does a similar process, trying to find a compatible buffer in memory, to reutilize the Id or, if one is not found, call the persister to save and return the generated Id.

The Cache generic class is the one with the capacity to serialize and deserialize buffers. It uses the CacheManager to read or write these buffers, but the Cache itself has its own WeakReference for the effective object. This is done because, when deserializing a cache object, only the id of the object is needed, not the real object. If too many "identical" objects are put in cache, all the cache objects can have their effective objects collected, but maybe the buffer to regenerate them is still in memory. It can look redundant, but in my experience, it is not.Well, so, your create a Cache for an object. The cache serializes the object and calls CacheManager, which will try to reutilize the id of some identical buffer or will ask for it to be saved... but, where will it be saved?

That's the job of the persister. Within the framework, the only Persister that exists and is already useable is the FileCachePersister. It simple receives the parameters and tries to load the file, if one exists, or returns null. It receives the name of the file and tries to update its date/time to keep it alive, or saves the file. This is done in such a way so that you can easily create a Persister to store data into the database, or use a remote server, which can have its own caching also, avoiding concurrent processes to access the same files at the same time. This is important, as the FileCachePersister works very well with many threads, but only ONE process must be using the directory.Ok, in the FileCachePersister there is a thread to delete the old files, but that's nothing really complex.

The ViewState

The ViewState solution is very similar to the Cache solution, but it also has additional security information. The class responsible for loading and saving ViewStates is the PfzPageStatePersister. Similar to the CacheManager class, it has a dictionary composed of SessionIds, so the ViewIds are exclusives to the actual session, then the values are dictionaries of ViewIds, and the values of that dictionary are the type of the page that generated the ViewState (so copying the ViewId to another page will not work) and the effective information of the ViewState.Or, better, a cache to such value. Why? Because if you go from one page to another, generating identical viewstates, only a new "reference" to the buffer will be generated, but the buffer, which can be very large, is the same. It looks a little more complicated, as it has a Pair, but that's because of the way PageStatesPersisters works in general, as they only generate two objects, which the only purpose to be serialized. Not very friendly to be honest.

But, then, the idea is the same:Look if the ViewState is in memory. If it is not, ask for the Persister to load it.When saving, search one identical ViewState in memory, or create a new one, calling the persister to save it.

Using the Code

The CacheManager class is where it all starts. And, if you use the framework only to save viewstates in files, is where it ends. In the Global.asax, put the following (or something similar):

Other Interesting Things

GCUtils - I already presented this class in another article, but it is modified now. Registering to the Collected event, you can be informed of a recently happened collection, if you have any additional memory you can free (like calling TrimExcess). The GCUtils.KeepAlive is also very useful, as a way to tell the GarbageCollector to avoid collecting recently used objects, even if they only have weak-references.

Cache class usage - You can use the Cache class even in Windows Forms, or if you really need to put some large information in the Session, instead of putting it on the ViewState.

For example, when you put the object in the session, you create the cache object and put the cache object in the session:

Session["MyVeryLargeItem"] = new Cache<byte[]>(newbyte[5000000]);

And, to read it, you do:

byte[] data = ((Cache<byte[]>)Session["MyVeryLargeItem"]).Target;

In normal code, you would still need to cast, but you will generally not create the cache and not cast it back to a cache to then read the target. But, this additional step will make your 5MB object become something like 32bytes in session, while the 5MB are kept in files.

Future Articles on the Same Theme

In future articles, I plan to explain with good examples the usage of the Cache class, the CacheDictionary, WeakDictionary and specially the StatedPage. But, those are part of my personal framework, not really a part of the ViewState solution, as they work independently of the ViewState, and can even be used in non-web applications.

For this article, I only wanted to show the ViewState solution, which really works (is in production for a long time now) and does not change the way programming is done, except for the fact that now you can paginate records and store the full dataset in ViewState, as it will not be sent to the client.

Share

About the Author

I started to program computers when I was 11 years old, as a hobbist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do they work easier, faster and with less errors.

Now I just started working as a Senior Software Engineer at Microsoft.

Here in my work I can't open this link... I will try to see it from home.

But, I think they could be combined.
See... the Pfz.Caching, before calling any persister, already tries to use buffers that are still in memory (so, avoiding unnecessary database calls and duplication of identical buffers).
Also, as it already calculates the checksum for you, it is easier for you to try to locate an identical buffer before saving it, again avoiding to waste space. And even two identical view-states will only generate 2 view-state references to the same buffer. Good isn't?

But, for the MongoDB... I don't know the database... and my original idea was just to avoid using any database reference... the Pfz.Caching framework works out-of-the-box with files or memory only.

I was able to see it now, but I must say the actual implementation has a very severe bug.

When you post-back a page, the view state of such page gets updated. Now, think about it:
You open "YourPage". It cames with the original view-state.
You change item 1. You get a new view-state.
You do a control+N.

Then, you want to do one thing in the first window (getting one view-state for it) and another thing for the second window (getting another view-state).
But, in such implementation, if you go to the first window, it will, in fact, point to the second view-state, as it's view-state was updated.

You don't even need to do that. Try going back some pages and posting again... instead of using the old-viewstate, the new one will be used... which can have incoherent state for the actual one.

But, I am not saying the database will not work... I am only saying that such implementation is not very good. If the database is really faster than others, using my library pointing to a MongoDb can get good results.

As you say: Run the code and see.
But, it is:
Don't send view-states to clients (so the pages get faster);
Reuse identical buffers (so many identical values, which are common, do not generate a lot of identical files, wasting hd);
Index buffers by checksum, so the search for identical buffers is fast;
Search in memory first, so there is no performance loss for getting everything from disk;
And, of course, allow information to be collected, so the server does not run out-of-memory.

But I know that's not the problem. You are ungry because I voted one in your article. That's happen. You are winning at the moment... 4 ones in my articles.

I have your code instaled on my deveelopment website and just had something strange happen. It somehow deleted the folders where the cache files were located.

I have a virtial folder named AppCache and under that folder are Buffers and Viewstates. It had been running perfectly and then after about 3 hours, AppCache,Buffers and Viewstates were deleted. Permissions disappeared with the folders so I received lost of errors.

Dis I install it wrong? Any suggestions as to where I should start looking?

Thanks again. I'll make the change and re-publish. I'll be waiting for your follow-up articles on you cacheing classes. Other than the problem that I mentioned (which was my problem) they work great and page load time is dramatically reduced.

Hi, in the readme in the "PfzAndPfzCaching-DllsSource.zip" there's a part stating:

Differences between versions
Trial - Has a fixed path for temporary files (C:\Temp\Pfz.Caching_TempFiles), is not prepared to be used by more than one process and only deletes temporary files during initialization. It is only useful to see how webpages get smaller and to see that identical viewstates (like reloading the same page) will not generate new files. This is also the only one compiled without optimizations and for x86 processor.

Professional-Single instance - Allows to change the temporary files directory, but is not prepared to be used by more than one process in the same directory. Also deletes unused files from time-to-time and, of course, does a "keep-alive" to recenlty used files, so they can live forever if they are used forever.

Professional-multiple instance - This can also be called the WebFarm version. It allows many processes to share the same directory and does not delete the files at startup, avoiding deletion of files created by other processes. The only special care needed is to configure all processes to use the same TotalProcesses value, but a different ProcessId. This version is also capable of continue to use it's own files after a restart.

License
The trial version can be used by anyone, without restrictions. The only requirement is to distribute all the package, including this document, when redistributing it.

The professional versions can't be redistributed and must have a license per server. It is not necessary to have a license to development machines, considering that development machines are not used as production servers.

The code provided is the whole version or just the trial one ? (With all the limitations)

It started as a commercial product, but I decided to give the main source-code for free and forgot to change the readme file.

Now, using the FileCachePersister (with has the source-code included) you have access to the "Professional-Single instance" with a plus. You can change the directory it uses, it deletes the unused files from time to time, recently used files are available even after a restart (this is the difference) and so, if the session is still alive (for example, you use State Server and do an iisreset), the viewstates are alive also, but it does not have a "manager" to avoid name conflicts if used by more than one site/server on the same directory.

I would only charge for the remote server, which allows to manage Sessions and ViewStates in a cached manner (yes, sessions included), remotelly and even allowing more than one project to share the same session if needed, so allowing for better modularization of projects.