Easier way to manage your ASP.NET Cache

I was recently told by a client of mine that the ASP.NET app I developed was WAY TOO SLOW! I had to agree; the site was pinging the database twice on every load of the home page. So I said, "Give me a week... I'll make it work better". I went home feeling bad... my app was slow and I really didn't know where to begin. I had a lot of code that depended on pinging the database and I didn't want to sift through it all. What I ended up doing was using the cache object.

I started to look at other open source projects and their approaches to caching. Than DotNetKicks' cache manager caught my eye! I went off that idea and created my own way of implementing an object to interface with the cache object, making it more manageable.

The Cache manager I wrote has 3 methods (Grab, insert, clear), a constructor and 2 properties (CacheKey, CacheDuration). So here's my code in VB and C#:

 

using System.Web;
using System.Web.Caching;
namespace MainSite.Cache
{
  public class CacheManager<T>
  {
    private string m_cachekey = "";
    public string CacheKey {
      get { return m_cachekey; }
      set { m_cachekey = value; }
    }

    private int m_cacheduration;
    public int CacheDuration {
      get { return m_cacheduration; }
      set { m_cacheduration = value; }
    }

    public CacheManager(string Key, int duration)
    {
      this.CacheKey = Key;
      this.CacheDuration = duration;
    }

    public T Grab()
    {
      return (T)HttpContext.Current.Cache(this.CacheKey);
    }

    public void Insert(T obj, System.Web.Caching.CacheItemPriority priority)
    {
      DateTime expiration = DateTime.Now.AddMinutes(this.CacheDuration);
      HttpContext.Current.Cache.Add(this.CacheKey, obj, null, expiration, TimeSpan.Zero, priority, null);
    }

    public void Clear()
    {
      HttpContext.Current.Cache.Remove(this.CacheKey);
    }

  }
}

Imports System.Web
Imports System.Web.Caching

Namespace MainSite.Cache
    Public Class CacheManager(Of T)

        Private m_cachekey As String = ""
        Public Property CacheKey() As String
            Get
                Return m_cachekey
            End Get
            Set(ByVal value As String)
                m_cachekey = value
            End Set
        End Property

        Private m_cacheduration As Integer
        Public Property CacheDuration() As Integer
            Get
                Return m_cacheduration
            End Get
            Set(ByVal value As Integer)
                m_cacheduration = value
            End Set
        End Property

        Public Sub New(ByVal Key As String, ByVal duration As Integer)
            CacheKey() = Key
            CacheDuration() = duration
        End Sub

        Public Function Grab() As T
            Return CType(HttpContext.Current.Cache(CacheKey()), T)
        End Function

        Public Sub Insert(ByVal obj As T, ByVal priority As System.Web.Caching.CacheItemPriority)
            Dim expiration As DateTime = DateTime.Now.AddMinutes(CacheDuration())
            HttpContext.Current.Cache.Add(CacheKey(), obj, Nothing, expiration, TimeSpan.Zero, priority, Nothing)
        End Sub

        Public Sub Clear()
            HttpContext.Current.Cache.Remove(CacheKey())
        End Sub

    End Class
End Namespace

 

So what I do is create a class and have a grab method that makes an instance of the cache manager object and calls the cache manager class' grab method. If the cache returns null, I have a private method in my class that does the nesessary things to put the info into the cache object. Here's an example:

namespace MainSite.Cache
{
  public class ReviewsCache
  {
    public static ReviewsCollection Grab()
    {
      CacheManager<ReviewsCollection> man = new CacheManager<ReviewsCollection>(GetKey(), 90);

      ReviewsCollection cont = man.Grab();

      if (cont == null) cont = Insert(man); 

      return cont;
    }

    private static ReviewsCollection Insert(CacheManager<ReviewsCollection> man)
    {
      ReviewsCollection cont = MainSite.Logic.Reviews.GetAll();
      man.Insert(cont, Web.Caching.CacheItemPriority.Default);
      return cont;
    }

    public static void Delete(string sectionname)
    {
      CacheManager<ReviewsCollection> man = new CacheManager<ReviewsCollection>(GetKey(), 90);
      man.Clear();
    }

    private static string GetKey()
    {
      return "Reviews";
    }

  }
}
Namespace MainSite.Cache
    Public Class ReviewsCache

        Public Shared Function Grab() As ReviewsCollection
            Dim man As New CacheManager(Of ReviewsCollection)(GetKey(), 90)

            Dim cont As ReviewsCollection = man.Grab()

            If cont Is Nothing Then cont = Insert(man)

            Return cont
        End Function

        Private Shared Function Insert(ByVal man As CacheManager(Of ReviewsCollection)) As ReviewsCollection
            Dim cont As ReviewsCollection = MainSite.Logic.Reviews.GetAll()
            man.Insert(cont, Web.Caching.CacheItemPriority.Default)
            Return cont
        End Function

        Public Shared Sub Delete(ByVal sectionname As String)
            Dim man As New CacheManager(Of ReviewsCollection)(GetKey(), 90)
            man.Clear()
        End Sub

        Private Shared Function GetKey() As String
            Return "Reviews"
        End Function

    End Class
End Namespace

 

The cache object is a great thing to utilize in ASP.NET. But to make the caching manageable, you need to have a structure for managing the cache object and add logic to delete, grab, etc. Interfacing with a class is easier and follows the MVC pattern better than just saying Cache["MyKey"] in your logic or UI.



kick it on DotNetKicks.com

26 Comments

  • WTF does your code have to do with MVC? It looks like Identity Map to me.

  • Joe,

    It had a model (CacheManager) and controllers that use the CacheManager. It's sorta like MVC... not really... but I like to think of it that way.

  • This has potential in-rush issues in a web farm scenerio. Refactoring your ReviewCache class to use locks and class variables would solve the problem.

  • @Chuck

    Yea... in my clients site, I did use locks. I wanted to keep it simple for this blog post though.

  • OK... who uses a webfarm other than enterprises? MOST people use a shared or 1 dedicates/virtual server. The Cache object is optimal for those people. If you want a webfarm sort of caching scenario, USE ENTERPRISE LIBRARY!

    I KNOW THIS HAS NOTHING TO DO WITH MVC!!! BUT THIS FOLLOWS THE PATTERN LOOSLY! I should have chosen my words better... whatever... give it a rest people.

  • Don't get me wrong Zack. Your manager is definatly very valuable for cache abstraction. I actually use a very similair implementation for it.

  • Hey um, give the dude a break already! I don't know this guy from Adam, but it's pretty clear he's trying to help people out with the fruit of his labor, no need to get all Britney Spears catty on him over scenarios he never claimed to address or semantics. I'm sure this code is very useful for some folks and a good starting place for others.

    Keep it up man.

  • I have to agree with Mike. There are way too many people who read blogs just to blast whatever the author puts on up in an effort to assist. I wish there was way to enforce the old saying; "If you don't have anything good to say, then just do say anything". Just my 2 cents.

  • Is this from Gavin or Zack? Anyways, I like the wrapper, but I'd prefer a more OO approach, with interfaces and implementors, it would be cleaner.

  • @Simone

    This would be written by me, inspired from Gavin.

    I could have use interfaces... but we should keep it simple!

  • Great article. I stumbled on it after impl'ing my own and realizing that someone has probably already done this. I added one nicety to mine that I don't see here. A delegate called "OnGetNewData" for the cachemgr that will fire when the internal cache returns NULL for a lookup. This allows the caller to subscribe to the event and handle the actual datafetch, then return back to the cachemgr for proper storage.

  • Zach. I know where you are coming from. You learn something new, create a class to make integration easy, then you tell the world about it. Only to have people tell you that its old stuff. You have good intentions. Keep it up! I remembered when I discovered the asp.net Cache Object. It was like I discovered a new religion, very exciting times. Your coding skills are a bit advanced to mine. I tend to just stick to simple functions. Here is how I do stuff in my helper functions.

    // add item to cache
    public void addToCache(string cachename, string data, int hours){
    Cache.Insert(cachename, data, null, DateTime.Now.AddHours(hours), System.Web.Caching.Cache.NoSlidingExpiration);
    }

    // get item from cache
    public string getFromCache(string cachename){
    return (string)Cache[cachename];

    }

    // in action with another helper function

    public string getBlogName(string blogid){
    string cachename="getBlogName_"+blogid;
    if(getFromCache(cachename)!=null){return getFromCache(cachename);}
    string data=countDB3("SELECT title FROM blogs where id='"+blogid+"'");
    addToCache(cachename, data, 12);
    return data;
    }


  • This was EXTREMELY helpful to me today!

  • @Dave

    YAY! That's awesome!

  • Hi Zack,

    Congrats for such an informative article. I too have a blog regarding ASP.NET Cache, it various uses, limitations and how to fix them. You are absolutely correct. Caching is an awesome way of speeding up apps.


  • This is a concise, informative article. It turned up in my search for Cache information and convinced me to implement such a class in my project. I'll be editing it somewhat to give the option of passing a CacheDependency object. Thanks for sharing!

  • Hi Zack!
    Nice post.

    I must point out however that ASP.NET Cache has some very intrinsic problems. Its not scalable for large server farms and its In-Process nature isn't reliable either.The right way to solve this scalability problem is through an in-memory distributed cache.

    Microsoft is finally realizing it. They're working on Velocity but that is still in its infancy and will take some time to stabilize and mature.

  • Thanks for the awesome code sample!

    I found one minor thing wrong with your C# example - you have to use brackets and not parens for accessing the cache like "HttpContext.Current.Cache[this.CacheKey]" instead of "HttpContext.Current.Cache(this.CacheKey)".

    Also, I was getting some NullReferenceExceptions in that method, so I modified it like so and it works nicely:

    public T Grab()
    {
    T temp;

    try
    {
    temp = (T)HttpContext.Current.Cache[this.CacheKey];
    }
    catch (NullReferenceException)
    {
    temp = default(T);
    }
    return temp;
    }

  • @Eric

    Yea... i was converting from VB to C# and didn't catch that. I'm not that much of a noob anymore :)

    I disagree with you on the NullReferenceException. If the value is null in the cache, why return something? The manager class is just an API that delegates the functionality of the HttpCache.

  • Well, you can feel free to disagree, but I had to do something because the exception was crashing my app :) For me this works, as I can test for the returned value safely now...

  • @Eric

    Just sayin that its not necessarily wrong to return null. Guess its a choice rather a black and white situation.

  • Nice one Zack, fits what I needed to a T :)

  • You put this here, so we could use it right? do you require any credit if we just copy/paste?

  • Naw, go ahead and copy paste. Code is "as is", so if it breaks I'm not liable :) But other than that, have at it!

  • Thanks for posting Zack. Simple and effective.

  • Here is also a nice post on how to clear cache in asp.net in few line. For more details please check out this link...
    http://mindstick.com/Blog/222/Clearing%20Cache%20in%20Asp%20net

    Thanks!!

Comments have been disabled for this content.