Best of both worlds - SessionCache

Published: 06 Nov 2006
By: Karl Seguin

The Session and Cache objects each have their own advantages making it tricky to know which to use for a given piece of data. SessionCache is a simple class meant to bring together the best of both worlds.

Introduction

When it comes to storing data, .NET developers have a veritable arsenal at their disposal. Aside from the System.Data, System.XML and System.IO namespaces, ASP.NET developers can store data in 5 main places: Application, Session, ViewState, Cache and Context. Each of these mechanisms have their own unique characteristics and uses. The main thing which separates most of them is their scope - some are long lived, some are short live, some are shared amongst all site users, others aren't.

In most cases, it's pretty clear when to use the ViewState and Context objects (although the Context object is generally underused by developers). It's also clear when data should be available to all users (Application) or is user-specific (Session). What isn't always clear is when the Cache object should be used rather than the Application or Session objects.

Cache Object

By default, the Cache object has the same scope as the Application - data stored in it is globally accessible to all users. It's quite easy, and common, to use the Cache to store session data. This is typically done by joining the cache's key with an id that's associated with a particular user, for example:

string key = string.Format("UserPermission:{0}", Session.SessionID);

Memory Friendly

What really makes the Cache object unique (since it clearly isn't its scope), is it's respect for system resources and flexibility. Specifically, both the Application and Session objects practically pin their data in memory. The Cache on the other hand uses a WeakReference, which behaves quite differently. For the most part, storing data in the Cache doesn't guarantee that it'll be there - even for a second. If necessary, .NET will dump items from the cache to free up memory - and believe me, if .NET needs to start freeing up memory, you want to let it do whatever it has to. I've worked with developers who thought they could manage memory better than .NET, the result of this hubris has typically been OutOfMemoryExceptions.

That's why whenever you retrieve a value from the Cache, you have to make sure it's not null. On the other hand, data stored in the Application or Session is going to be there when you pull it out. If you've used the Cache before, you are probably familiar with this type of defensive coding:

string key = "AllUsers";
List<User> users = (List<User>)HttpRuntime.Cache[key];
if (users == null)
{
   users = new DataProvider().GetAllUsers();
   users = users ?? new List<User>();
   HttpRuntime.Cache.Insert(key, users, null, DateTime.Now.AddMinutes(10), 

Caching.NoSlidingExpiration);
}
return user;

It might seem like a nuisance, but the benefits are worth it. In 95% of the cases, the Cache object is better to use than either the Application or Session objects. In some cases you'll have a relatively light chunk of data which is expensive to calculate/fetch/deduce. In this case, pinning data in memory might be an ideal solution. Even in those cases though, items inserted in the Cache can be marked with High or even NotRemovable priority.

Functionality

Aside from not being harmful to a system's stability, the Cache object is more flexible and powerful than either the Session or Application object. The ability to specify an absolute or sliding timeout, file or database dependency, callback method and a priority are all unique to the Cache object. The only thing the Cache object doesn't have is the ability to specify where the data is stored - it's always in-memory. As you probably know, it's possible to configure the Session to use either memory, state server or SQL Server - although this has to be configured for the entire application.

Not Web-Related

Finally, the last benefit the Cache object provides is that it can be safely used outside of a web context. If you have a class library, say for your business layer, you can reference System.Web and use HttpRuntime.Cache. Some developers have a hard time doing this within their Business layer, but it's completely safe. In all honesty, Microsoft should consider moving the Cache object outside of the System.Web assembly to help developers get over the shock of seeing System.Web references inside a library.

The reference returned by HttpRuntime.Cache is the same which is exposed by the Page property and HttpContext.Cache - so you can use them interchangeably. In fact, HttpContext.Cache actually does:
public Cache Cache()
{
   get { return HttpRuntime.Cache; }
}

(Caching data inside the business layer can make it more difficult to effectively unit test code. But there are ways to work around, and even, with it.)

SessionCache

The only problem with the Cache is that using it for session-based information can be a little messy. Always having to append a unique identifier to the key when inserting and retrieving data can be a pain. It would be nicer to encapsulate that within a class and have a clean API to work with. The SessionCache class does exactly that. It's a very straightforward class which simply wraps the Cache object. Here's a partial implementation:

namespace Fuel
{
   public class SessionCache
   {
      #region Fields and Properties
      private string _uniqueId;
      public object this[string key]
      {
         get { return HttpRuntime.Cache[CreateKey(key)]; }
         set { Insert(key, value); }
      }
      #endregion
   
      #region Constructors
      public SessionCache()
      {         
         if (HttpContext.Current == null)
         {
            Init(Guid.NewGuid().ToString());
         }
         else
         {
            Init(HttpContext.Current.Session.SessionID);
         }
      }
      public SessionCache(string uniqueId)
      {
         Init(uniqueId);
      }
      private void Init(string 

uniqueId)
      {
         if (string.IsNullOrEmpty(uniqueId))
         {
            throw new ArgumentNullException("uniqueId");
         }
         _uniqueId = uniqueId;
      }
      #endregion
   
      #region Inserts
      public void Insert(string key, 

object data)
      {
         HttpRuntime.Cache.Insert(CreateKey(key), data);
      }
   
      public void Insert(string key, 

object data, CacheDependency dependency)
      {
         HttpRuntime.Cache.Insert(CreateKey(key), data, dependency);
      }
      //more inserts
      #endregion
   
      #region Private Methods
      private string CreateKey(string 

key)
      {
         if (string.IsNullOrEmpty(key))
         {
            throw new ArgumentNullException("key");
         }
         return string.Format("{0}:{1}", key, _uniqueId);
      }
      #endregion
   }
}

Inside a User Class

If you have a User class within your project, you can even take it a step further and expose an instance of SessionCache as one of its properties. Here's an example:

namespace Fuel
{
   public class User
   {
      private int _userId;
      private SessionCache _cache;

      public SessionCache Cache
      {
         get { return _cache; }
      }
      
      //used internally to re-create a user from the database
      internal User(int userId)
      {
         _userId = userId;
         _cache = new SessionCache(_userId.ToString());
      }
   }
}

Consumers can then use a very clean API to store and retrieve user-specific information from the cache:
//store
user.Cache.Insert("Roles", roles);
//retrieve
user.Cache["Roles"];

Summary

The Cache object has distinct advantages over both the Application and Session objects. It's more scalable and flexible without any real penalty. The SessionCache is a simple layer of abstraction which wraps the Cache object into a lightweight shell, resulting in code that's easier to read, use and manage when dealing with session-based data.

About Karl Seguin

Karl Seguin is an senior application developer at Fuel Industries, located in Ottawa, Ontario. He's an editor here at DotNetSlackers, a blogger for the influential CodeBetter.com and a Microsoft MVP.

View complete profile

Top Articles in this category

JavaScript with ASP.NET 2.0 Pages - Part 1
ASP.NET 2.0 has made quite a few enhancements over ASP.NET 1.x in terms of handling common client-side tasks. It has also created new classes, properties and method of working with JavaScript code. This article explores the enhancements and the various ways of injecting JavaScript programmatically into ASP.NET 2.0 pages.

ASP.NET ComboBox
The ASP.NET ComboBox is an attempt to try and enhance some of the features of the Normal ASP.NET DropDownList.

Upload multiple files using the HtmlInputFile control
In this article, Haissam Abdul Malak will explain how to upload multiple files using several file upload controls. This article will demonstrates how to create a webform with three HtmlInputFile controls which will allow the user to upload three files at a time.

JavaScript with ASP.NET 2.0 Pages - Part 2
ASP.NET provides a number of ways of working with client-side script. This article explores the usage and drawbacks of ASP.NET script callbacks, and briefly presents a bird's view of ASP.NET AJAX.

Using WebParts in ASP.Net 2.0
This article describes various aspects of using webparts in asp.net 2.0.

Top
 
 
 

Please login to rate or to leave a comment.

Product Spotlight