Implementing custom FileCacheProvider

This is a summerized version of what you can find in the APress book Chapter 11.

If you want to cach some pages or all, you need to first tell the ASP.NET to do so. As the caching starts before rendering the page, you can not specifiy this in the page markup, rather it will be mentioned in the Global.asax.cs
        public override string GetOutputCacheProviderName(HttpContext context)
        {
            // Get the page.
            string pageAndQuery = System.IO.Path.GetFileName(context.Request.Path);
            if (pageAndQuery.StartsWith(“OutputCaching.aspx”))
                return “FileCache”;
            else
                return base.GetOutputCacheProviderName(context);
        }
This tells to ASP.NET to use FileCache mechanics for cachin OutputCaching.aspx page. The rest will just follow the default.
Next step is to specify where to find the implementation of FileCache. This is mentioned in the web.config as follows:
    <system.web>
      <caching>
      <outputCache defaultProvider=”FileCache”>
        <providers>
          <add name=”FileCache” type=”MyCompany.MyProject.Caching.FileCacheProvider” 
               cachePath=”~/Cache” />
        </providers>
      </outputCache>
    </caching>
  </system.web>
Notice that the cachePath is not a recognized attribute but we may introduce it to get the parameters. In this case it refers to an existing folder on the root of the website.
Next step is to implement the provider. This involves to implement a class that inherits from OutputCacheProvider and overrides the methods as follows:
    public class FileCacheProvider : OutputCacheProvider
    {
        // The location where cached files will be placed.
        public string CachePath
        { get; set; }
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection attributes)
        {
            base.Initialize(name, attributes);
            // Retrieve the web.config settings.
            CachePath = HttpContext.Current.Server.MapPath(attributes[“cachePath”]);
        }
        public override object Add(string key, object entry, System.DateTime utcExpiry)
        {
            // Transform the key to a unique filename.
            string path = ConvertKeyToPath(key);
            // Set it only if it is not already cached.
            if (!File.Exists(path))
            {
                Set(key, entry, utcExpiry);
            }
            return entry;
        }
        public override object Get(string key)
        {
            string path = ConvertKeyToPath(key);
            if (!File.Exists(path)) return null;
            CacheItem item = null;
            using (FileStream file = File.OpenRead(path))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                item = (CacheItem)formatter.Deserialize(file);
            }
            // Remove expired items.
            if (item.ExpiryDate <= DateTime.Now.ToUniversalTime())
            {
                Remove(key);
                return null;
            }
            return item.Item;
        }
        public override void Set(string key, object entry, System.DateTime utcExpiry)
        {
            CacheItem item = new CacheItem(entry, utcExpiry);
            string path = ConvertKeyToPath(key);
            // Overwrite it, even if it already exists.
            using (FileStream file = File.OpenWrite(path))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(file, item);
            }
        }
        public override void Remove(string key)
        {
            string path = ConvertKeyToPath(key);
            if (File.Exists(path)) File.Delete(path);
        }
        private string ConvertKeyToPath(string key)
        {
            // Flatten it to a single file name, with no path information.
            string file = key.Replace(‘/’, ‘-‘);
            // Add .txt extension so it’s not confused with a real ASP.NET file.
            file += “.txt”;
            return Path.Combine(CachePath, file);
        }
    }
The CachePath will be set by configurationManager. The private method ConvertKeyToPath helps to find a unique filename based on the url. This example uses binary formatter and a wrapper class that adds an expiration property to any object that needs to be cached. So, the following is the implementation of the wrapper class:
    [Serializable]
    public class CacheItem
    {
        public DateTime ExpiryDate;
        public object Item;
        public CacheItem(object item, DateTime expiryDate)
        {
            ExpiryDate = expiryDate;
            Item = item;
        }
    }
That’s all. When you run this you will notice that you will get an error if the Cache folder does not exist in the root.
To test the caching try to put the next code in the Default.aspx
    <%@ OutputCache Duration=”10″ VaryByParam=”None” %>