Monday, September 7, 2009

Easier way to manage your ASP.NET Cache

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.

No comments: