Prototyping a Cache/Disk Serializer

In the 4.0 release of the .NET framework, one of the enhancements I'm most looking forward to is the extensibility of the ASP.NET cache.  Up until 4.0, the caching system was built directly into ASP.NET, and although it was possible to use outside of ASP.NET, it certainly wasn't easy.  And while .NET 2.0 (and up) has a ProviderBase class to use as a point of extensibility, it's a bit of a task if you want to build a robust caching provider.  With 4.0, this will be much easier and more robust.

But I'm not on 4.0 ... yet.

I don't want to over-design a full fledged system, but what I needed to do was prototype a simple disk-based caching class.  Essentially, a simple way to serialize objects to disk.   While I do make extensive use of the ASP.NET cache in my project, I needed to supplement it because it's just not durable enough.  Reboots, app recylces, memory pressure ... all of these are minor considerations if building these cached resources are not time intensive.    But what if you want to cache a "large" item for a week?  (I say large not just in memory footprint, but also in resources used to construct such an object.)

So here were some of my considerations:
- Cache object will be the main source of the cache.  If cache miss, check "durable" cache.  It cache miss, reconstruct.
- K.I.S.S.  4.0 will solve a lot of my problems.  Not looking to spend a lot of time on this.  (Writing this blog post will take more time than writing the code.)
- Need to implement async capabilities. 
- Need to implement proper locking. 

The first step was to prototype (below) a simple serialization mechanism that handles the serialization/deserialization to disk.   .NET generics help make this a bit smoother, and I chose to use binary serialization primarily for speed over XML, but also flexibility.  Error handling needs to be flushed out, and there's an assumption that there's a single folder (Globals.DiskCachePath) for all objects.  Also, the class uses the HostingEnvironment instead of the HttpContext to MapPath since their won't be a context in an async thread.  This behavior would be abstracted in a more robust solution ... but for simplicity, this works fine.


public class DiskCacher<T>
    {
        public DiskCacher() { }

        public static void SerializeToFile(string name, T container)
        {
            string filepath = HostingEnvironment.MapPath(
                Globals.DiskCachePath + name);

            Stream stream = null;

            try
            {
                stream = File.Open(
                   filepath, FileMode.Create,
                   FileAccess.ReadWrite, FileShare.None);
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, container);
            }
            catch (Exception ex)
            {
#if DEBUG
                throw;
#endif
            }
            finally
            {
                if (stream != null)
                {
                    stream.Close();
                }
            }
        }

        public static DateTime Deserialize(string name, out T item)
        {
            item = default(T);

            Stream stream = null;

            string filepath = HostingEnvironment.MapPath(
                 Globals.DiskCachePath + name);

            if (!System.IO.File.Exists(filepath))
            {
                return DateTime.MinValue;
            }

            try
            {
                System.IO.FileInfo fi = new System.IO.FileInfo(filepath);
                DateTime lastUpdated = fi.LastWriteTime;

                stream = File.Open(filepath, FileMode.Open,
                    FileAccess.Read, FileShare.Read);
                BinaryFormatter bFormatter = new BinaryFormatter();
                item = (T)bFormatter.Deserialize(stream);

                return lastUpdated;
            }
            catch (Exception ex)
            {
#if DEBUG
                throw;
#endif
                return DateTime.MinValue;
            }
            finally
            {
                if (stream != null) stream.Close();
            }
        }

    }

The two methods are static for simplicity.  I debated using an indexer to store/retrieve, but felt ultimately that functionality would belong to the provider, not the utility methods.  Not worth investing in until 4.0. 

Using this is pretty simple.  The type of the object becomes somewhat irrelevant (as long as it can be serialized via the binary formatter).  The simplest example would be:

string myName = "Brian";
DiskCacher<string>.SerializeToFile("cachename", myName);

... and then to rehydrate:

string myName;
DateTime lastUpdated = DiskCacher<string>.Deserialize("cachename", out myName);

Returning the lastUpdated time allows the provider to make a decision as to whether or not expire the item.   A bit more of a realistic example would be:

List<Customers> customers = GetMyCustomers();
DiskCacher<List<Customers>>.SerializeToFile("mycustomers", customers);

and...

List<Customers> customers;
DateTime lastUpdated = DiskCacher<List<Customers>>.Deserialize("mycustomers", out customers);

Generics are certainly not required for this type of solution but do make things easier.  The provider itself would offer a cleaner interface for end users, but from a prototype perspective, this was pretty successful. 

Conclusion:  I think .NET 4.0 will offer some great extensibility for disk-based caching and memcaching.  Can't wait to play with it!

Comments are closed

My Apps

Dark Skies Astrophotography Journal Vol 1 Explore The Moon
Mars Explorer Moons of Jupiter Messier Object Explorer
Brew Finder Earthquake Explorer Venus Explorer  

My Worldmap

Month List