Gal Ratner
Gal Ratner is a Techie who lives and works in Los Angeles CA and Austin TX. Follow galratner on Twitter Google
Write your own OutputCacheProvider

What is output caching?


Output caching is a way of caching the generated HTML created from your ASP.NET pages. Output caching can cache an entire page or fragments of a page. The first request to a page is served dynamically, and then any subsequent requests are served from the output cache until the specified time expires.


how to use output caching?


It’s simple, just place the @ OutputCache directive like so <%@ OutputCache Duration="120" VaryByParam="None" %>, or programmatically set the PartialCachingAttribute on your pages or controls. Remember, once you have done that, any postbacks in the page will not work.


Where that ASP.NET keep the cached output?


By default ASP.NET keeps the cache in memory, you can change this to keep the cache in files, a remote server or anywhere you like by overriding the default OutputCacheProvider and writing your own implementation.


Creating your own OutputCacheProvider


In order to create a custom cache provider you need to inherit from the class OutputCacheProvider  and override four methods: Add, Get, Remove and Set


Add: Inserts the specified entry into the output cache. If an entry already exists, this method should return the value already in the cache.
Get: Returns a reference to the specified entry in the output cache.
Remove: Removes the specified entry from the output cache.
Set: Inserts the specified entry into the output cache, overwriting the entry if it is already cached.


For my example implementation I chose a smart cache system. I have four modes users can set:


Memory: will keep the cache in memory. I chose a ConcurrentDictionary since it is thread-safe.
Files: each item will be stored in a file in the cache directory. I am going to use a BinaryFormatter in order to read and write objects from and to disk.
Compressed: same as files, however, to save disk space, objects will be stored as zip files.
Auto: will alternate between memory and files modes. Memory will be used if the amount of available memory on your system is more than 60 percent, beyond that, the cache will identify your system is getting low on memory and switch to files mode.

 Let’s take a look at the class:

/// <summary>

        /// Inserts the specified entry into the output cache.

        /// </summary>

        /// <param name="key"></param>

        /// <param name="entry"></param>

        /// <param name="utcExpiry"></param>

        /// <returns></returns>

        public override object Add(string key, object entry, DateTime utcExpiry)

        {

                     // If the item exists, return the item

                     object existingItem = Get(key);

                     if (existingItem != null)

                           return existingItem;

 

                     // If the item doesnt exist. Add it to the cache

                     Set(key, entry, utcExpiry);

                     return entry;

        }

 

        /// <summary>

        /// Returns a reference to the specified entry in the output cache.

        /// </summary>

        /// <param name="key"></param>

        /// <returns></returns>

        public override object Get(string key)

        {

            CacheMode cacheMode = Utils.GetCacheMode();

            var cacheItem = Utils.ConcurrentCache.AsParallel().Where(c => c.Key == key && c.Value.UtcExpiry >= DateTime.Now.ToUniversalTime()).FirstOrDefault();

            if (cacheItem.Key == null)

                return null;

 

            if (cacheMode == CacheMode.Compressed)

                return IO.ReadCompressedFile(key);

            else if (cacheItem.Value.ItemValue != null) // Check the memory first

                return cacheItem.Value.ItemValue;

            else

                return IO.ReadFile(key);

        }

 

        /// <summary>

        /// Removes the specified entry from the output cache.

        /// </summary>

        /// <param name="key"></param>

        public override void Remove(string key)

        {

            CachItem cacheObject = new CachItem();

            Utils.ConcurrentCache.TryRemove(key, out cacheObject);

            string fileKey = Utils.GetCacheFileName(key);

            if (File.Exists(Utils.CacheLocation + fileKey))

                File.Delete(Utils.CacheLocation + fileKey);

        }

 

        /// <summary>

        /// Inserts the specified entry into the output cache, overwriting the entry if it is already cached.

        /// </summary>

        /// <param name="key"></param>

        /// <param name="entry"></param>

        /// <param name="utcExpiry"></param>

        public override void Set(string key, object entry, DateTime utcExpiry)

        {

            CacheMode cacheMode = Utils.GetCacheMode();

            CachItem cacheObject = new CachItem() { UtcExpiry = utcExpiry };

 

            if (cacheMode == CacheMode.Compressed)

                IO.WriteCompressedFile(key, entry);

            else if (cacheMode == CacheMode.Files)

                IO.WriteFile(key, entry);

            else

                cacheObject.ItemValue = entry;

 

            Utils.ConcurrentCache.AddOrUpdate(key, cacheObject, (oldKey, oldValue) => cacheObject);

        }

 

 Configuration changes:

Inside web.config locate system.web and add:

<caching>

  <outputCache defaultProvider="ConcurrentCache">

    <providers>

      <add name="ConcurrentCache" type="ConcurrentCache.ConcurrentCache"/>

    </providers>

  </outputCache>

</caching>

 I have attached ConcurrentCache.cs to this post and also added the entire example solution to codeplex at http://concurrentcache.codeplex.com/ so feel free to check it out and plug it into your applications.

Shout it kick it on DotNetKicks.com


Posted 6 Jun 2010 4:53 AM by Gal Ratner
Attachment: ConcurrentCache.cs

Powered by Community Server (Non-Commercial Edition), by Telligent Systems