After writing the same 20 lines of boilerplate code for the 1000th time, I decided to encapsulate it in a class and add some syntactic sweetness to it in the form of a fluent interface as recently demonstrated by Joey Beninghove.
What I had been doing was something like the following pseudocode (or a slight variation) in every place I wanted to cache something.
declare returnObject as null
if the HTTPCache available
if the object is available in the cache
set returnObject
else
set returnObject to whatever code is needed to get the object
add returnObject to the cache
else
set returnObject to whatever code is needed to get the object
return returnObject
Here is the class I ended up with.
public class HTTPCacheHelper<T>
{
private DateTime? _AbsoluteExpiration;
private TimeSpan? _SlidingExpiration;
private CacheItemPriority _Priority = CacheItemPriority.Default;
private string _Key;
/// <summary>
/// Key of item stored in the cache.
/// </summary>
public HTTPCacheHelper<T> WithKeyOf(string key)
{
_Key = key;
return this;
}
/// <summary>
/// Absolute date to expire on
/// </summary>
/// <remarks>
/// Optional, but either this or WithSlidingExpiration must be called
/// </remarks>
public HTTPCacheHelper<T> WithExpirationDateOf(DateTime expires)
{
_AbsoluteExpiration = expires;
return this;
}
/// <summary>
/// Sliding timespan to expire on.
/// </summary>
/// <remarks>
/// Optional, but either this or WithExpirationDateOf must be called
/// </remarks>
public HTTPCacheHelper<T> WithSlidingExpiration(TimeSpan ts)
{
_SlidingExpiration = ts;
return this;
}
/// <summary>
/// Priority
/// </summary>
public HTTPCacheHelper<T> WithPriorityOf(CacheItemPriority priority)
{
_Priority = priority;
return this;
}
/// <summary>
/// Adds the item to the cache based data passed in in previous methods.
/// This will the last method call in the chain, and must
/// be called in order to for the item to be cached.
/// </summary>
public void Commit(T item)
{
Debug.Assert(!item.Equals(default(T)));
Debug.Assert(!_AbsoluteExpiration.HasValue ^ _SlidingExpiration.HasValue);
Debug.Assert(!string.IsNullOrEmpty(_Key));
if (!_AbsoluteExpiration.HasValue) _AbsoluteExpiration = Cache.NoAbsoluteExpiration;
if (!_SlidingExpiration.HasValue) _SlidingExpiration = Cache.NoSlidingExpiration;
lock (HttpRuntime.Cache)
{
HttpRuntime.Cache.Add(_Key, item, null, _AbsoluteExpiration.Value, _SlidingExpiration.Value, _Priority, null);
}
}
public T GetItem(string key)
{
if (HttpRuntime.Cache != null)
{
object check = HttpRuntime.Cache[key];
if (check != null)
{
return (T)check;
}
}
return default(T);
}
private bool IsNull(T item)
{
if (typeof(T).IsValueType)
{
return default(T).Equals(item);
}
else
{
return item == null;
}
}
public T GetItem(string key, Func<T> itemSourceOnDoesNotExist)
{
T check = GetItem(key);
if (IsNull(check))
{
check = itemSourceOnDoesNotExist();
}
return check;
}
public T GetItem(string key, Func<T> itemSourceOnDoesNotExist, Proc<T> addToCache)
{
T check = GetItem(key);
if (IsNull(check))
{
check = itemSourceOnDoesNotExist();
addToCache(check);
}
return check;
}
}
The methods prior to Commit() make up the fluent interface, and I stole a little code from Ayende's Rhino Commons for the delegates in the GetItem methods. As a side note, the GetItems methods were originally static, which allowed me to do the following:
private string ItemToCache()
{
return "expected value";
}
[Test]
public void FluentSyntaxText()
{
HTTPCacheHelper<string> cache = new HTTPCacheHelper<string>();
cache
.WithExpirationDateOf(DateTime.Now.AddDays(1))
.WithKeyOf("TestKey")
.WithPriorityOf(CacheItemPriority.Low)
.Commit(ItemToCache());
string actualValue = HTTPCacheHelper<string>.GetItem("TestKey", ItemToCache, cache.Commit);
Assert.AreEqual("expected value", actualValue);
}
But it dawned on me a little later that by making them instance methods I could accomplish the same thing in a single statement
[Test]
public void FluentSyntaxTest2()
{
HTTPCacheHelper<string> cache = new HTTPCacheHelper<string>();
string actualValue = cache
.WithExpirationDateOf(DateTime.Now.AddDays(1))
.WithKeyOf("TestKey")
.WithPriorityOf(CacheItemPriority.Low)
.GetItem("TestKey", ItemToCache, cache.Commit);
Assert.AreEqual("expected value", actualValue);
}
Which is pretty friggin' sweet.