Twitter Feed Popout byInfofru

OverrideThis.com

Adventures in .NET Software Craftsmanship!

Caching Abstraction Solution - .NET 3.5

Today, as I was writing the frontend to an ASP.NET web application I noticed a trend in my code.  I had the following block of code in one form or another in several places.

protected string MemberFullName() {

    // build the cache key.
    string cacheKey = string.Format("Member@FullName@{0}"
        , this.Page.User.Identity.Name);

    // build cache entry.
    if (this.Cache[cacheKey] == null) {

        // get membership service.
        var membershipService = DependencyFactory.MembershipService();

        // get member.
        var member = membershipService
            .MemberGetByUsername(this.Page.User.Identity.Name);

        // get full name.
        string fullName = string.Format("{0}, {1}", 
            member.Lastname, 
            member.Firstname);

        // add to cache.
        this.Cache.Insert(cacheKey, 
            fullName, 
            null, 
            DateTime.Now.AddMinutes(5), 
            TimeSpan.Zero);
    }

    // return value.
    return (string)this.Cache[cacheKey];
}

 
Immediately I decided to address the issues that could arise from letting this behavior continue. First of all, I wanted to decouple the direct dependency to the ASP.NET Caching implementation, that way if in the future I decided to cache using another provider I would be able to make the switch without huge problems. I also wanted to use some of the cool features that have been available since the release of the .NET 3.0 Framework like Lambdas, and some oldies but goodies, like Generics and Anonymous Methods.

Step 1 - I coded a basic enum to use as the type of information to be cached.

public enum CacheKey {
    MembershipRolesByUsername,
    MembershipFullNameByUsername,
}


Step 2 -  I built the following interface that will provide the abstraction.

public interface ICacheProvider<T> {
    T GetItemFromCache(CacheKey type, string key, int minutes, Func<T> builderFunction);
}


Generics is used to add flexibility to the Cache provider while retaining the elegance and compile time advantages of strongly typed code. It has a couple of parameters but the one that should really catch your eye is the builderFunction (Func<T>) which allows us to pass in the function used to create the item to cache by using a Lambda expression or an Anonymous Method.

Step 3 - Implement the cache provider.

public class CacheProvider<T> : ICacheProvider<T> {

    #region ICacheProvider Members

    public T GetItemFromCache(CacheKey type, 
                 string key, 
                 int minutes, 
                 Func<T> builderFunction) {

        // build the cache key.
        string cacheKey = string.Format(string.Concat(type.ToString(), "{0}"), key);

        // get current cache.
        var context = HttpContext.Current;

        // build cache entry.
        if (context.Cache[cacheKey] == null)
            context.Cache.Insert(cacheKey, 
                 builderFunction.Invoke(), 
                 null, 
                 DateTime.Now.AddMinutes(minutes), 
                 TimeSpan.Zero);

        // return value.
        return (T)context.Cache[cacheKey];
    }

    #endregion
}


Nothing real fancy to explain here, the only thing I would point out is that the function to build the item doesn't get called if the item is already in the cache which is the whole point of caching.

Step 4 - Use the Cache provider
Option A - With an anonymous method.

// anonymous function.
Func<string> memberFullNameFunction = delegate() {
     var member = DependencyFactory.MembershipService()
          .MemberGetByUsername(this.Page.User.Identity.Name);
     return string.Format("{0},{1}", member.Lastname, member.Firstname);
};

// return value.
return new CacheProvider<string>()
     .GetItemFromCache(CacheKey.MembershipFullNameByUsername
          , this.Page.User.Identity.Name
          , 5
          , memberFullNameFunction);


Option B - With a Lambda Expression

// return value.
return new CacheProvider()
    .GetItemFromCache(CacheKey.MembershipFullNameByUsername
        , this.Page.User.Identity.Name
        , 5
        , ()  => {
                var member = DependencyFactory.MembershipService()
                    .MemberGetByUsername(this.Page.User.Identity.Name);
                return string.Format("{0},{1}", 
                    member.Lastname, 
                    member.Firstname);
        });


Depending on personal taste I guess one way of calling the provider might seem simpler than the other.

Thanks for reading,

Roberto Hernandez