Introduction

A Session Wrapper - Barely needs and introduction :-)

The problems that a session wrapper is wielded to solve are (at least) two-fold

  • The avoidance of "Magic Strings" in the code (for the Session object reference keys)
    • They are easy to mistype - Spelling, capitalization etc. Not to mention, they'll quickly be scattered all over the place, and no one will have a clue which and how many are actually in use. It's horrible.
  • Achieving intellisense for the objects stored in the Session object.
    • Intellisense for the objects stored in the Session object eliminates a lot of the problems above, and makes everyone more productive at the same time. It also tells everyone what you can (are allowed to) throw into the Session object in the first place.

In the following I will start with a very simple example, and then slowly build it up to be more generic and thus more useful and easier to deploy with the objects you want to wrap.

Background

The impetus for writing this article was the need for Yet-Another-Session-Wrapper for a potential project of mine.

"Been there - Done that" a few times already, but each time it was one of those monolithic session wrappers that simply threw everything into one custom class.

This time I wanted to do it a bit differently, as that approach never really sat to well with me. How an object stores itself should be a property of the object itself, i.e. I have an object A, so I tell A to store itself (somehow) rather than go looking for whatever custom Utility Saving Tool has been constructed for this app. That simply makes better OO-coding sense to me (YMMV).

You can skip this next section. Seriously. I'm simply rambling about why you are reading this in the first place

So some quick Google-fu was in order to see what other people might have come up with that I could steal use (hey I'm a programmer, I'm lazy :-)). Now it's dead easy to roll a monolithic session wrapper, so obviously that's pretty much all I found. It was sort of interesting to see that even this simple a concept could be designed in quite a few ways, each with their strengths and weaknesses. It runs the whole gamut from simply just adding some custom properties to the session wrapper custom object, to also employing singleton patterns and generics in the more advanced versions.

Still they all shared the same property of being simple monolithic beasts tossing good OO out the window (in my opinion). Which wasn't what I was looking for.

In the end I found just one example of an OO Session Wrapper (my definition), and that example was poor to say the least. So many many code-smells in that code (method consisting of just one line that just calls another method. And no params to make a difference).

I was considering linking it, but that probably wouldn't be fair. At least the dude had the right idea about the approach to the problem, and he made the effort to post it, namely an OO Session Wrapper.

Now, there's probably some good examples out there of what I was looking for, but my Google-fu just wasn't finding them. So, "Time to Roll Your Own".

The Code - Take #1

All Take#1 code examples are in the SessionWrapperSimple project, along with an .aspx showing some simple test of functionality.
  public class MyClass {
    public int x { get; set; }
    public int y { get; set; }
    public int z { get; set; }
    
    // *** Various MyClass Methods below ***
  }

This is our starting point.

Note the use of the nifty Automatic Properties feature - keeps things nice and terse.

So - We have some class, and we want to store objects of this class in Session. Below is the very simple code for the general idea behind an OO Session Wrapper.

  public class MyClassWrappedv1 {
    private static string key = "MyClassWrappedv1";
    
    public int x { get; set; }
    public int y { get; set; }
    public int z { get; set; }
    
    //Constructor - Stores the object in Session.
    public MyClassWrappedv1() {
      HttpContext.Current.Session[key] = this;
    }
    
    //If MyClassWrappedv1 doesn't exist already it will return null
    public static MyClassWrappedv1 Stored {
      get { return HttpContext.Current.Session[key] as MyClassWrappedv1; }
    }
    // *** Various MyClass Methods below ***
  }

If you just need something quick and dirty, you can stop here. Simply chuck the very few lines of code in and that's that.

Using the code

//Instantiate a new object - Same as you'd always do.
MyClassWrappedv1 MyWrappedv1A = new MyClassWrappedv1 { x = 1 };
Note the use of the nifty Object Initializer feature - keeps things nice and terse.
  //Retrive the Stored MyClassWrappedv1 instance.
  //Note how this is an actual static *property* of the object itself
  MyClassWrappedv1 MyWrappedv1A = MyClassWrappedv1.Stored;
  
  //Assign some value to a property of the object
  //This writes directly into the object stored in the Session.
  //No need to get it and cast it and save it to Session again
  MyWrappedv1A.x = 1;

As with a monolithic session wrapper, with the OO session wrapper you also need to determine before hand if you want to "session wrap" a given type of object - It's not an entirely "free meal".

In a simple monolithic session wrapper you would add a field of the type of the object class and then add the corresponding getter and setter.

With the OO Session Wrapper, you add the above code directly to your object class instead (substituting for the correct class names) and you're good to go. It's very straightforward.

The Code Take #1 - Combo

One (of several) "inadequacies" of the above simple example is it's only designed for Session use. It's of course a trivial matter to replace Session with Application to use the Application object instead.

It does however point out a limitation - That you need to decide at design time where you want to store your object.

The next piece of code allows you to decide at runtime where you want to store your object (Session or Application). It has to be one or the other though. If you "change your mind", say from Session to Application (when you new up a new object), you will lose the implicit object reference to the object stored in Session (You can of course still have an explicit variable reference to it)

First we want an enum that we can use to distinguish between Session and Application (again to avoid any "Magic Strings" in our code)
  public enum StorageLocation {
    Session,
    Application
  }
And the code to manage if an object goes into Session or Application is of course a simple if/else construct based on the static location field
  public class MyClassWrappedCombov1 {
    private static string key = "MyClassWrappedCombov1";
    private static StorageLocation location = StorageLocation.Session;
    
    public int x { get; set; }
    public int y { get; set; }
    public int z { get; set; }
    
    //Constructor - Stores the object in session.
    public MyClassWrappedCombov1(StorageLocation sl) {
      if (sl == StorageLocation.Session) {
        HttpContext.Current.Session[key] = this;
        location = StorageLocation.Session;
      }
      else { // StorageLocation.Application
        HttpContext.Current.Application[key] = this;
        location = StorageLocation.Session;
      }
    }
    
    //If MyClassWrappedCombov1 doesn't exist already it will return null
    public static MyClassWrappedCombov1 Stored {
      get {
        if (location == StorageLocation.Session) {
          return HttpContext.Current.Session[key] as MyClassWrappedCombov1;
        }
        else { // location == StorageLocation.Application
          return HttpContext.Current.Application[key] as MyClassWrappedCombov1;
        }
      }
    }
    // *** Various MyClass Methods below ***
  }

Using the Code

//Instantiate a new object - Same as you'd always do.
//Now with the added param of type StorageLocation
MyClassWrappedCombov1 MyWrappedCombov1A = new MyClassWrappedCombov1(StorageLocation.Session) { x = 5 };

//Retrive the Stored MyClassWrappedCombov1 instance.
//No change here
MyClassWrappedCombov1 MyWrappedCombov1B = MyClassWrappedCombov1.Stored;

Points of Interest

So far we haven't done anything too interesting. It's all fairly simple and straightforward, but the code is there so you can copy/paste away without having to download everything, if that is all you need for some simple stuff. 

Some observations

We already have achieved the goals we set out to accomplish

  • Remove "Magic Strings" from our general code.
  • Achieve intellisense for our stored objects (it's implicit as the wrapper is baked in to the objects)

It's also worth mentioning that it's fairly trivial to expand it from memory storage to other types (file/DB what have you) - Although in it's current form I would not recommend it unless you have a very specific need.

A nice thing about the OO Session Wrapper implementation is also that the casting of the objects pulled from Session (that stores everything as Object) is handled within the type itself, as compared to a monolithic session wrapper that would have to handle all types of objects (fairly trivial, but not as nice)

So as such we are done with the OO Session Wrapper. Except for some... 

Issues

I mentioned earlier that the code as it is has some "inadequacies". One minor nibble is that the key for the object is still "done in hand" albeit inside the class, and thus not visible in our general code.

This is trivially solved with a typeof(MyClass) in the assignment of course, which will be done in the following. I left it as a literal string simply for demonstration purposes.

One major glaring issue is that if we want to persist two instances of the same type we are in trouble - We can't. The creation of a new object will automatically overwrite a prior persisted object (of the same type).

The next section will explore the solution for that issue.

The Code Take #2 

All Take#2 code examples are in the SessionWrapperBase project, along with an .aspx showing a minimal usage test

A naive solution to the issue of being unable to save two instance of the same type, would be to simply copy/paste the class definition and give it a slightly different name. Done.

This of course gives rise to potentially enormous amounts of identical code. Especially if we want to persist both 3,4,5 etc objects of the same type at the same time. There's a codesmell if I ever saw one.

Sub Classing to the rescue of course.

Pick whichever of the code examples from above that suits your taste and needs the best and then rename the class to MyBaseClass or similar - and then subclass away.

Something like this -
  // **** This code does NOT work. Do not use. For demo purpose only!!! ****
  public class MyBaseClass {
    private static string key = typeof(MyBaseClass).ToString();
    
    public int x { get; set; }
    public int y { get; set; }
    public int z { get; set; }
    
    //Constructor - Stores the object in session.
    public MyBaseClass() {
      HttpContext.Current.Session[key] = this;
    }
    
    //If MyBaseClass doesn't exist already it will return null
    public static MyBaseClass Stored {
      get { return HttpContext.Current.Session[key] as MyBaseClass; }
    }
    
    // *** Various MyClass Methods below ***
  }
  
  public class MyClass1 : MyBaseClass { };
  public class MyClass2 : MyBaseClass { };
  public class MyClass3 : MyBaseClass { };
  public class MyClass4 : MyBaseClass { };

As the first line says - This won't work like this.

Two problems: For one, each subclass would receive/use the same key - As such we have accomplished nothing. Secondly, the Stored return type is MyBaseClass, which you can't assign to the subclasses, i.e. MyClass1 mc1 = MyBaseClass.Stored (Does not work). And MyBaseClass is of course completely agnostic about which subclass is currently in use.

If only there was a way... To "transfer" the type of the sub class to the base class preferably as a variable (as it will vary) somehow...

It just so happens that that's pretty much the (quite liberal :-)) description of Generics

If you are unfamiliar with Generics, follow the above link. It's as good a primer as any.

With a bit of Generic magic we can quickly transform the above to the following

  public class MyBaseClass<T> where T : class {
    private static string key = typeof(T).ToString();
    
    public int x { get; set; }
    public int y { get; set; }
    public int z { get; set; }
    
    //Constructor - Stores the object in session.
    public MyBaseClass() {
      HttpContext.Current.Session[key] = this;
    }
    
    //If MyBaseClass doesn't exist already it will return null
    public static T Stored {
      get { return HttpContext.Current.Session[key] as T; }
    }
    // *** Various MyClass Methods below ***
  }
  public class MyClass1 : MyBaseClass<MyClass1> { };
  public class MyClass2 : MyBaseClass<MyClass2> { };
  public class MyClass3 : MyBaseClass<MyClass3> { };
  public class MyClass4 : MyBaseClass<MyClass4> { };

Really not that big a deal and now it does exactly what we want. The key will be unique for each subclass, so they don't override each other. Further, each sub class is empty so it's very fast an easy to add new ones if needed.

It in fact takes less code now than with a monolithic session wrapper that would need both a property declaration and the corresponding getter/setter.

The "where T : class" bit is needed as we are using a cast to T in Stored.

Using the code  

//Instantiation hasn't changed.
MyClass1 mc1 = new MyClass1 { x = 1 };
//Nor has anything else
mc1 = MyClass1.Stored;
mc1.x = 2;

You can of course fairly easy expand the above to use any of the (too) numerous combinations demonstrated in The Code Take#1. I'm not going to include all that again :-)

We are not quite done yet though - One improvement remains :-)

The Code Take #3 (and last)

All Take#3 code examples are in the SessionWrapper project, along with an .aspx showing a minimal usage test

The one thing that remains, is to improve the re-usability of the code. As it stands now we have to copy/paste the session wrapper code into each of the classes of the objects we want to persist. It's seriously not a lot of code lines - But you know, you might change your mind one day on where you want to persist your objects...

To achieve an extra level of decoupling and thus re-usability we are going to sub class yet again. The trick being to isolate the object initialization code and other methods, from the actual session wrapper code.

  // *************** SessionWrapper ***************
  public class SessionWrapper<T> where T : class {
    private static string sessionvar = typeof(T).ToString();
    public SessionWrapper() {
      HttpContext.Current.Session[sessionvar] = this;
    }
    
    //If T doesn't exist already it will return null
    public static T Stored {
      get { return HttpContext.Current.Session[sessionvar] as T; }
    }
  }
  
  // *************** MyClass ***************
  public class MyBaseClass<T> : SessionWrapper<T> where T : class {
    public int x { get; set; }
    public int y { get; set; }
    public int z { get; set; }
    
    // *** Various MyBaseClass Methods below ***
  }
  
  public class MyClass1 : MyBaseClass<MyClass1> { };
  public class MyClass2 : MyBaseClass<MyClass2> { };
  public class MyClass3 : MyBaseClass<MyClass3> { };
  public class MyClass4 : MyBaseClass<MyClass4> { };

And to show the re-usability (no changes to the session wrapper code needed) lets have another class, that this time even takes a constructor parameter - This does add a bit to the subclass declaration code, but it's manageable - and it's still a one-liner even with the rather long class names :-)

  // *************** MyOtherClass ***************
  // ---- Demonstrates needed extra code if the constructor takes arguments -----
  public class MyOtherBaseClass<T> : SessionWrapper<T> where T : class {
    public string a { get; set; }
    public string b { get; set; }
    public string c { get; set; }
    
    // Constructor with argument
    public MyOtherBaseClass(string s) {
      this.c = s;
    }
    
    // *** Various MyOtherBaseClass Methods below ***
  }
  
  public class MyOtherClass1 : MyOtherBaseClass<MyOtherClass1> { public MyOtherClass1(string s) : base(s) { } };
  public class MyOtherClass2 : MyOtherBaseClass<MyOtherClass2> { public MyOtherClass2(string s) : base(s) { } };
  public class MyOtherClass3 : MyOtherBaseClass<MyOtherClass3> { public MyOtherClass3(string s) : base(s) { } };
  public class MyOtherClass4 : MyOtherBaseClass<MyOtherClass4> { public MyOtherClass4(string s) : base(s) { } };

Both MyClassN and MyOtherClassN will work with no changes needed to the session wrapper. So the session wrapper can now be safely tucked away somewhere in the code base, and never be touched again.

Using the code  

//Everything is as always
//Initializing the x property - Can be left out of course
MyClass1 mc1A = new MyClass1 { x = 1 };

//Calling with a constructor value (can not be left out), and initializing the a property (can be left out)
MyOtherClass1 moc1A = new MyOtherClass1("555") { a = "5" };

Closing thoughts

The above only shows the wrapper for the session object, but it's straighforward making a copy and then replace Session with Application, and then inherit from that wrapper instead

Other wrappers might need more specialized code (saving to file, DB etc) but they are just as easy to plug in where you need them

You can even chose to create different base classes each using their flavour of wrapper, and the subclasses can in turn in their declarations pick whichever is suitable. The caveat here is that you'd need to duplicate all you BaseClass code to have the choice between wrappers at runtime. Not ideal, so pick your poision.

One last "embellishment" one could add, and I'll leave that as a readers exercise :-), is to add a toggle as to whether you want the object persisted or not when you're new()'ing one up - I think that would prove rather useful :-)

Overall I feel the final design presented is surprisingly flexible and powerful, even with an absolute minimum of coding needed.
I know *I* was surprised when I finally finished it. It's so... simple and elegant (IMHO) :-)

I'm actually also rather surprised I didn't find anything else like it out there - Would have saved me a ton of hours writing this article!

Enjoy.

History

  • Version 1 - 2011-03-14
  • Code Take #1 - Combo :  Stored v2 had Session twice instead of Session/Application. (update your source file as needed - Although you shouldn't really use this approach) 
  • 2011-03-16 : All references to the (ultimately) flawed version 2 of Stored have been removed. See the messages for more info.
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"