[X]

Asp.Net 4.0 - Custom Output Caching Provider (Design Patterns in Action)

Desing Patterns in Action

Today we will develop a custom output caching provider in asp.net 4.0. In .net framework 4.0 microsoft has emphasized on provider model for pluggable development. The provider model in .net 4.0 has been used and promoted extensively.

Developers can write their custom provider for logging, membership, caching, viewstate without affecting the existing application and plug that up with just configuration changes.

I will try to show this by writing custom file based output caching for asp.net 4.0 application. There may be scenario where memory based caching is expensive, so we can make use of provider model and write our own custom output caching.

Let's start-

1. Create a C# Library Project

2. Add reference to System.Web dll

3. Create a class which will be used to keep track of cache expiry like below:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CustomOutputCaching
{
    class FileBasedCache
    {
        public string FileName { get; set; }
        public DateTime UtcExpiry { get; set; }

        public FileBasedCache() { }
        public FileBasedCache(string fileName, DateTime utcExpiry)
        {
            FileName = fileName;
            UtcExpiry = utcExpiry;
        }
    }
}

 

 

4. Create FileBasedOutputCachingProvider class and inherit it from OutputCacheProvider and override the methods as below:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.Hosting;
using System.IO;

namespace CustomOutputCaching
{
    public class FileBasedCachingProvider : OutputCacheProvider
    {
        static readonly object _synLock = new object();

        private Dictionary<string, DiskOutputCacheItem> _fileBaseCacheItem = new Dictionary<string, FileBasedCache>();
        private Dictionary<string, DiskOutputCacheItem> FileBasedCacheItem
        {
            get
            {
                return _fileBaseCacheItem;
            }
        }

        private string _fileBasedCacheFolder = HostingEnvironment.ApplicationPhysicalPath + @"CachedItems\";
        public string CacheFolder
        {
            get
            {
                return _fileBasedCacheFolder;
            }
        }

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            if (!string.IsNullOrEmpty(config["FileBasedOutputCachePath"]))
            {
                _fileBasedCacheFolder = config["FileBasedOutputCachePath"];
                if (!_fileBasedCacheFolder.EndsWith(@"\"))
                    _fileBasedCacheFolder += @"\";

                config.Remove("FileBasedOutputCachePath");
            }

            base.Initialize(name, config);
        }

        public override object Add(string key, object entry, DateTime utcExpiry)
        {
            // check if cache object already exists
            var results = this.Get(key);
            if (results != null)
                return results;
            else
            {
                //create the cache object
                this.Set(key, entry, utcExpiry);

                return entry;
            }
        }

        public override object Get(string key)
        {
            FileBasedCache item = null;
            CacheItems.TryGetValue(key, out item);

            if (item == null)
                return null;

            if (item.UtcExpiry < DateTime.UtcNow)
            {
                this.Remove(key);

                return null;
            }

            return GetCacheData(item);
        }

        public override void Remove(string key)
        {

            FileBasedCache item = null;
            this.CacheItems.TryGetValue(key, out item);

            if (item != null)
            {
                 try
                {
                    RemoveCacheData(item);

                    CacheItems.Remove(key);
                }
                catch { }
            }
        }

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

            var item = new FileBasedCache(this.GetSafeFileName(key), utcExpiry);
            System.Threading.Thread.Sleep(2000);



            lock (_synLock)
            {
                WriteCacheData(item, entry);
                if (this.CacheItems.ContainsKey(key))
                    this.CacheItems[key] = item;
                else
                    this.CacheItems.Add(key, item);
            }
        }

        protected virtual object GetCacheData(FileBasedCache item)
        {
            var cachedFile = Path.Combine(this.CacheFolder, item.FileName);

            if (File.Exists(cachedFile))
            {
                using (var stream = new FileStream(cachedFile, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    return OutputCache.Deserialize(stream);
                }
            }
            else
                return null;
        }

        protected virtual void RemoveCacheData(FileBasedCache item)
        {
            var fileToRetrieve = Path.Combine(this.CacheFolder, item.FileName);
            if (File.Exists(fileToRetrieve))
                File.Delete(fileToRetrieve);
        }

        protected virtual void WriteCacheData(FileBasedCache item, object entry)
        {
            var cachFile = Path.Combine(this.CacheFolder, item.FileName);

            using (var stream = new FileStream(cachFile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
            {
                OutputCache.Serialize(stream, entry);
            }
        }

        protected virtual string GetSafeFileName(string unsafeFileName)
        {
            var safeFileName = unsafeFileName;

            foreach (char c in Path.GetInvalidFileNameChars())
                safeFileName = safeFileName.Replace(c.ToString(), "_");

            return safeFileName;
        }
    }
}

 

 

5. Create a sample website project

 

6. Get the dll from previous project and put in Bin folder of the website project

 

7. Modify the web.config file of the website project as below:

 

<system.web>
	  <caching>
		  <outputCache defaultProvider="FileCache">
			  <providers>
				  <add name="FileCache" type="CustomOutputCaching.FileBasedCachingProvider, 

CustomFileBasedOutputCachingProvider"/>
			  </providers>
		  </outputCache>
	  </caching>
</system.web>

 

 

8. Add folder to the website project named "CachedItems"

 

9. Add a page to the website project and enable OutputCaching by adding Cache directive (Duration="number of seconds") on top of the page as below

 

<%@ OutputCache Duration="3600" VaryByParam="*" %>

 

 

Now run your website project and see that a file is created in "CachedItems" folder with the name of the page for which you have enabled output caching. If you remove the Caching setting from web.config file, again your page caching will start getting stored in memory instead of disk.

So this was the simple working example of custom file based output caching in asp.net 4.0 using provider design pattern.

Provider pattern is also used supporting multiple database provider and switching between them at run time based on the configuration settings. Most of the time that is achieved using Ado.Net DbFactory classes.

blog comments powered by Disqus

Posts By Month