Monday, December 13, 2010

Container-based Pseudosingletons in ASP.NET Applications

There are three different ASP.NET server-side containers which can be used to store the data at the server side:

  • Application – it’s a shared container for the whole application (a collection of static data)
  • Session – it’s a user-session based container, identified by a session identifier (appended to HTTP responses in a cookie) so that it stores user-specific data
  • Items – it’s “even more fine grained session”, it’s not only specific to the user but also to the single request so the container is emptied just after the processing ends on the server and before the response is sent to the client

There’s an interesting pattern behind server-side containers I call Pseudosingleton. The pattern makes is easier to access the container data by wrapping it in a static property with a sole “get” accessor. But instead of using a static backend field (like Singleton does), Pseudosingleton stores the data in one of the three server-side containers.

Let’s just see an example of storing the ORM session context in the Items container.

public class Global
{
   const string MODELKEY = "model";
 
   /// A pseudosingleton for the data access layer
   /// This one uses the Items container so that the object
   /// is created ONCE-PER-REQUEST
   ///
   /// The structure of the code mimics the implementation
   /// of the Singleton pattern but uses the container
   /// to hold the initialized object
   public static TheDatabase Model
   {
      get
      {
         if ( HttpContext.Current.Items[MODELKEY] == null )
         {
             // create the object somehow
             TheDatabase db = new TheDatabase();
             
             // store in the container
             HttpContext.Current.Items[MODELKEY] = db;
         }
         
         return (TheDatabase)HttpContext.Current.Items[MODELKEY];   
      }
   } 
}
 
/* This would be the data model of the ORM of your choice */
public class TheDatabase : System.Data.Linq.DataContext 
{
}

What would be the benefit of such approach? Well, the client code can access the object just like there’s only one, single instance and the actual reference is initialized once per items/session/application (depending of the container used):

public class BusinessLayer
{
   public void BusinessProcess()
   {
      // access the data
      var users = Global.Model.Users.Where( u => u.Name == .... );
 
      // continue processing and use Global.Model anytime you access the data
      // note that because Items container has been used, the actual ORM session is initialized
      // once per request and thrown away when the processing ends on the server-side
 
      ...
   }
}

This really makes the client code clean, understandable and maintanable. Also, the implementation of the static field can be changed anywhere (for example to use a different server-side container) but the client code does not change.

The pseudosingleton can also easily be created for the Session container:

public class SessionPseudoSingleton
{
   const string DATAKEY = "datakey";
 
   public static Foo TheData
   {
      get
      {
         if ( HttpContext.Current.Session[DATAKEY] == null )
         {
            // create object somehow
            Foo f = new Foo();
            HttpContext.Current.Session[DATAKEY] = f;
         }
 
         return (Foo)HttpContext.Current.Session[DATAKEY]; 
      }
   }
}
 
public class BusinessProcessing
{
   public void TheProcess()
   {
       // access the data using:  
       SessionPseudoSingleton.TheData
       ...
   }
}

and for the Application container. This time, however, you have to take care of the possible concurrent execution of two (or more) threads accessing the data. Suppose the application pseudosingleton is written exactly like the two above and two concurrent threads execute the “if (HttpContext.Current.Application[DATAKEY] == null )” one after the other.

Of course, the container will be empty for both threads and both of them will happily continue the execution of the “if” body, thus initializing the data object twice!

To prevent such unintended behavior, an additional trick is required, known as “a lock with two lookups”:

public class ApplicationPseudoSingleton
{
   const string DATAKEY = "datakey";
   const object _lock = new object();
 
   public static Foo TheData
   {
      get
      {
         // first - check the container in an usual way
         if ( HttpContext.Current.Application[DATAKEY] == null )
         {
            // if it's empty - synchronize threads so that only one 
            // is allowed to continue at one time
            lock ( _lock )
            {
               // let the only thread create the data in the container
               // when other threads are allowed to continue, they will
               // not enter the "if" body
               if ( HttpContext.Current.Application[DATAKEY] == null )
               {
                  Foo f = new Foo();
                  HttpContext.Current.Application[DATAKEY] = f;
               } 
            }
         }
 
         return (Foo)HttpContext.Current.Application[DATAKEY];
      }
   }
}

That’s it. Now, when we are able to create pseudosingletons in an uniform way for all server-side containers, two interesting questions arise.

First question : which containers can be used to store the ORM sessions in real-world applications? The answer is – the Items container is the best choice. ORM sessions are created once per requests and there’s no memory footprint on the server as more and more client requests are served. The session container is a bad choice just because of the memory consumption. You probably could have your ORM sessions stored in the session container for every single user but only assuming that you have few users. The application container, on the other hand, could be an interesting choice and I saw at least one application which does so. There are few pros, like the ORM session is created only once and it’s shared among all requests. The cons are obvious – as more and more clients access the data, the object grows in memory and ultimately the whole database is retrieved and stored in memory. This could crash the server because of the lack of resources. Concluding – the Items container is your best choice.

Second question would be – is there a chance to dispose the data stored in a pseudosingleton? Many objects which could be stored there implement IDisposable which should be called somewhere to prevent nasty leaks. And the answer is: for the two containers, Items and Session, there are event handlers in the Application class which can be used to dispose the data.

For example, for the Items container, the corresponding event would be the EndRequest event:

/* global.asax */
public class Global : HttpApplication
{
   ...
   
   protected void Application_EndRequest( object sender, EventArgs e )
   {
 
      // check the container manually. calling Global.Model would always initialize the data!
      if ( HttpContext.Current.Items["modelkey"] != null )
      {
         // now when I know that the container is not empty, I can call Global.Model
         // with no risk of unintented initialization
         Global.Model.Dispose();
      }
   }   
}

Happy coding.

No comments: