How To Boost your Sites Performance With Configurable Caching

In today’s article, I’m going to go over how you can implement a global caching solution that can switch between Global, Session and Disabled.  On any web project, performance is key.  Your website users will go elsewhere if a page takes too long to load.  One easy to implement feature to improve this is to cache objects that are expensive to generate.  If you can have something in the server cache rather than having to go back to one or more databases, your page load will be speeded up considerably.

The other issue with designing caching at the start of a project is sometimes you don’t know where the best place to store it is.  Let’s say you cache a list of users in your website, storing their user data usually makes sense to stick it in Session state but what happens if a requirement comes along later that you need to do reporting about real time data.  In this case it might make more sense to store some of your data globally to access it.

If you want to write your software according to good SOLID principles you don’t want to have to change your user profile code when these changes in requirements crop up, and what happens if you decide to completely turn off caching later on when the site is in maintenance mode ?  So our problem then turns into how do write our code so it’s flexible enough to handle it?

The Caching Code

Below defines the code to cache our value types and references in cache . The code is pretty standard .net code.  One class uses MemoryCache to store things and one uses Session state.  They both implement the same ICacher interface so we can program to an interface later on.

Memory Caching

public class MemoryCacher : ICacher
{
public object GetValue(string key)
{
var memoryCache = MemoryCache.Default;
return memoryCache.Get(key);
}
public bool Add(string key, object value, DateTimeOffset absExpiration)
{
var memoryCache = MemoryCache.Default;
return memoryCache.Add(key, value, absExpiration);
}
public void Delete(string key)
{
var memoryCache = MemoryCache.Default;
if (memoryCache.Contains(key))
memoryCache.Remove(key);
}
}

Session Caching

public class SessionCacher : ICacher
{
public object GetValue(string key)
{
return HttpContext.Current.Session == null
? null
: HttpContext.Current.Session[key];
}
public bool Add(string key, object value, DateTimeOffset absExpiration)
{
if (HttpContext.Current.Session == null)
return false;
HttpContext.Current.Session[key] = value;
return true;
}
public void Delete(string key)
{
if (HttpContext.Current.Session == null)
return;
HttpContext.Current.Session[key] = string.Empty;
}
}

Cache Interface

public interface ICacher
{
object GetValue(string key);
bool Add(string key, object value, DateTimeOffset absExpiration);
void Delete(string key);
}

Cache Factory

Now we have the code to handle our caching, the next bit is to create a way we can swap the type of caching via configuration data. This is done by a standard factory.  I’ve used an enum as the switch to ensure the code can’t break as easily but you could use a sting if you wanted to.

public class CachingFactory : ICachingFactory
{
public ICacher GetCacheHelper(CacheType cacheType)
{
ICacher cacher;
switch (cacheType)
{
case CacheType.Memory:
cacher = new MemoryCacher();
break;
case CacheType.Session:
cacher = new SessionCacher();
break;
default:
cacher = null;
break;
}
return cacher;
}
}

The interface will look like this:

public interface ICartRepositoryFactory
{
ICartHelperWrapper GetCartHelper();
}

The enum will look like this:

public enum CacheType
{
Disable,
Memory,
Session
}

Global Values Need To Be Unique

Some of you may have come to the conclusion the values that need to be stored globally need to be unique otherwise they will be overridden by other users data.  One simple way to get around this is to prefix the User Id before the cache identifier.  As of .Net 4.5 onwards even anonymous users are assigned a Guide so we can use that. We don’t want to have to write this key generation code throughout our solution, if we ever wanted to change the key our lives would be horrible.

To get round this I created an abstract caching helper. The sole point of this is to generate the cache key. I can then create a different implementation of the helper for each value I want to add into the cache.

public abstract class BaseCachingHelper
{
protected readonly ICachingFactory CachingFactory;
protected ICacher Cacher;
protected BaseCachingHelper(ICachingFactory cachingFactory)
{
CachingFactory = cachingFactory;
}
protected abstract string CacheIdentifier { get; }
public void ClearCache(string userId)
{
Cacher.Delete(GenerateCacheKey(userId));
}
protected virtual string GenerateCacheKey(string userId)
{
return string.Format("{0}:{1}", CacheIdentifieruserId, userId);
}
protected virtual void ClearAndAddToCache(string cacheValue, string cacheKey)
{
Cacher.Delete(cacheKey);
Cacher.Add(cacheKey, cacheValue, new DateTimeOffset(DateTime.Now.AddDays(1)));
}
}

Notice how we’re passing in the cache factory into the constructor.  This means we can now swap between the different types of cache without having to modify the use profile class.

Now let’s show an example of an instance of the BaseCachingHelper looks like.

public class UsernameCachingHelper : BaseCachingHelper, IUsernameCachingHelper
{
protected const string CACHE_IDENTIFIER = "Username";
public UserCurrencyCachingHelper(ICachingFactory cacheFactory, CacheType cacheType)
: base(cacheFactory)
{
Cacher = CachingFactory.GetCacheHelper(cacheType);
}
protected override string CacheIdentifier
{
get
{
return CACHE_IDENTIFIER;
}
}
public void AddUsernameToUsersCache(string userId, string username)
{
var cacheKey = GenerateCacheKey(userId);
ClearAndAddToCache(username, cacheKey);
}
public Currency GetUsernameFromUsersCache(string userId)
{
var cacheKey = GenerateCacheKey(userId);
var value = Cacher.GetValue(cacheKey);
return value != null ? new Currency(value.ToString()) : null;
}
}

We can then set up an app config value or use your dependency injection container to set the factory before it gets injected. This then allows you to switch between the cache without having to update any of your code.

container.For<IUsernameCachingHelper >().Use(ctx => new UsernameCachingHelper (new CachingFactory(), CacheType.Session));

Conclusion

We’ve talked about the different ways you can cache data in a Microsoft .NET web environment, in a specific users session so it is only available to them or globally and shared between all sessions.

We created a factory to wrap these two caches so we could switch between them without having to change any code.  Lastly we created a caching helper to automatically stamp a cache key with the currency user identifier so when we do switch the session keys can work globally and not get overwritten on each request.

Jon D Jones

Software Architect, Programmer and Technologist Jon Jones is founder and CEO of London-based tech firm Digital Prompt. He has been working in the field for nearly a decade, specializing in new technologies and technical solution research in the web business. A passionate blogger by heart , speaker & consultant from England.. always on the hunt for the next challenge

More Posts

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *