DISQUS

Just Sayin' More Words: Pluggable ASP.NET CacheManager

  • Maarten Oosterhoff · 1 year ago
    Great post, I've got something similar on my end.
    One thing though, and that is that in your implementations of the Get-method you access the storage twice. One time to check if there is anything in storage, and another time to actually get it. Since you don't lock anything, another thread can remove the item you are looking for. This will happen when you use the storage frequently.

    Something like the following will solve this.

    public T Get<T>(string key)
    {
    T item = (T)HttpContext.Current.Cache[key];
    if (item == null)
    item = default(T);
    return item;
    }
  • John Sheehan · 1 year ago
    Thanks for the Explanation Maarten!
  • Marisic.Net · 1 year ago
    Only one thing I'd add to the code Maarten posted, I would recommend using the AS operator instead of a direct case, and make it a one line operation.

    return HttpRuntime.Cache[key] as T;

    This will automatically return default(T) if the cached item doesn't exist. Using Maarten's code if you try to get an object from the cache that can not correctly cast to the type T you will get can't convert exception for the object.
  • John Sheehan · 1 year ago
    The method is Generic, not the class, so you can't use as with T (The type parameter 'T' cannot be used with the 'as' operator because it does not have a class type constraint nor a 'class' constraint). You could add a type constraint, but it would limit what types you could specify when calling the method.
  • Marisic.Net · 1 year ago
    I forgot about the as operator having a constraint of having to be a reference type, let me amend my post:

    var item = HttpRuntime.Cache[key];
    return item is T ? (T) item : default(T);

    Which is basically what the as operator does anyway.
  • Marisic.Net · 1 year ago
    Steve,

    I can offer some insight into the model that John was following when he created the cache manager. Following a pattern similar to this allows you to create loosely coupled code.

    With your question if you implemented the caching solution using only the specific classes if you ever decided you wanted to change your caching backing store from HttpRuntime.Cache (note: John, you should reference the cache as HttpRuntime.Cache instead of Httpcontext.Current.Cache, calling the context just causes extra processing to just resolve to HttpRuntime.Cache) to a sql data store or to a memory caching solution like memcache or velocity you will now have to go into you code and change every single usage of RequestProvider to use MemCacheProvider or whatever other implementation you wish to use.

    With a loosely coupled implementation that John created here, if you ever wish to switch from one provider to another you only ever need to change where it instantiates CacheManger to use the new provider instead. This brings me to my point about Enterprise Library's CacheManager having a factory method for creating the caching providers usage, this will allow you to only need to declare the CacheProviders once in your code and no matter how many times you change the concrete implmentation of ICacheProvider you will only ever need to change 1 line of code in the entirety of your project which is a great thing indeed.

    This idea of creating shared services that you can plug and play based off of interfaces is the basis of the idea of "Inversion of Control" or IoC that creates very robust and completely decoupled projects. Some of the most well known IoC frameworks are Microsoft's Unity, Spring.NET, Castle Windsor, Ninject, StructureMap to name a few there are quite a bit of frameworks out there for IoC. Creating loosely coupled code is definitely getting to the point where it is mandatory for a project to be a well made solution.
  • John Sheehan · 1 year ago
    Great explanation Chris! Much better than I would have done.
  • Steve · 1 year ago
    No you don't, what is the difference between

    ICacheProvider cache = new ShortTermProvider();

    or

    CacheManager cache = new CacheManager(new ShortTermProvider());
  • John Sheehan · 1 year ago
    That is correct. However, if your CacheManager contains additional logic that is provider-independent (not demonstrated here), then you'd want to use the example code.
  • Steve · 1 year ago
    If you want to add provider-independent code you can perfectly use something like an adapter, which implements ICacheProvider (which you could have done with CacheManager) and takes a ICacheProvider instance and then proxy the calls and do your extra logic.
  • kevinpang · 1 year ago
    I use a very similar approach for session variables as well.
  • Steve · 1 year ago
    What's the point in using CacheManager instead of just using the ICacheProvider instances?
  • Marisic.Net · 1 year ago
    This is a nice implementation of a basic caching solution. You should also be aware of the Enterprise Library Caching Application Block. it offers extensibility to add new cache providers and is very easy to integrate with other Enterprise Library blocks, from my own personal usage I used it with the Membership provider for caching user tokens/privileges.

    http://msdn.microsoft.com/en-us/library/cc30910...

    The big difference in implementation of the Caching block is that the providers are created through a singleton factory which in my opinion makes alot of sense since cache stores generally are only ever 1 and only 1 per type of store so relying on a factory to handle the management of instances of the providers makes sense to me more than doing new __Provider(....) anytime you wish to access the cache.
  • Brian · 1 year ago
    I've written something of the sort a while back..(not as good as yours), but I did something like this:

    public static Cache Cache {
    get {

    if (System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Cache != null)
    return System.Web.HttpContext.Current.Cache;

    return System.Web.HttpRuntime.Cache;

    }
    }

    That allows me to utilize the same concepts in Windows Forms and Windows Service applications....just some FYI
  • flalar · 1 year ago
    Why do you call the method destroy??
  • duy lam · 1 year ago
    CacheManager cache = new CacheManager(new ShortTermProvider()); // the constructor of CacheManager should be uses in looser way.

    You should take a look at some Dependency Injection framework (castle, spring.net, Nunit) and apply it instead of creating new CacheManager directly in code.
  • Betty · 1 year ago
    Have you put any thought into this IoC approach when you require a CacheDependency? I basically took your approach and created a new Store method that took CacheDependency as a parameter.

    I'm a fan of the whole loosely coupled thing, however there are some times that I require (well, want more then require) a few different caching strategies for one project. Very data dependent of course. Any thoughts?
  • John Sheehan · 1 year ago
    The problem with using parameter is that if you have a lot of calls in a class, you have to update them all if you want change providers.

    The example above is perfect for if you need different caching strategies in a project. Just create a provider for each strategy and use that provider when needed.
  • Bart Czernicki · 1 year ago
    In other words....its the strategy pattern for caching? :)
  • John Sheehan · 1 year ago
    I honestly didn't realize that this was the Strategy Pattern (I'm not well versed in patterns), but you are correct sir.
  • Paul · 1 year ago
    Just a quick one on this...

    lock the cache when you're writing to it, for occasions where data is being refreshed by another process.

    lock (HttpContext.Current.Cache)
    {

    }
  • John Sheehan · 1 year ago
    Can you elaborate on this a little more? I've never come across anyone
    discussing locks and caching (at least in regards to ASP.NET) before. I
    think it would be interesting to hear what the risk is of not using a lock.
  • smnbss · 1 year ago
    I suggest you to implement a new method to implement the "read through" caching mechanism
    public delegate T GetObjectDelegate<T>();
    public V GetItemValue<V>(string objectID, V nullValue, GetObjectDelegate<V> getobject)
    {
    if (Cache[objectID] == null)
    {
    if (getobject == null)
    return default(V);

    V obj = getobject();
    if (obj != null)
    {
    Insert(objectID, obj);
    return obj;
    }
    else
    return nullValue;

    }
    return (V)Cache[objectID];
    }

    in this way you'll be able to verify if an object is in cache and load the object in cache if it's not just in a single line

    CacheManager.GetItemValue("key", null, delegate(){return getobjfromdb();})
  • Edmartinez · 11 months ago
    Hi! Great Explanation John! ASP.NET is one of my favorite subjects. Its performance is good but only on a single server environment as we try to scale out; it will not work smoothly. But distributed caching solves this problem. Distributed caching is scalable and highly reliable. Even Microsoft also realized it and come up with its own solution of distributed caching. I think we should look into it.