Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

ASP.NET OO SessionWrapper - As an integral part of the objects themselves

4.20/5 (4 votes)
17 Mar 2011CPOL16 min read 42.6K   167  
An ASP.NET OO Session wrapper baked into the objects themselves, instead of a monolithic wrapper containing all objects to persist.

Introduction

A Session wrapper - barely needs an 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 artucle, 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 a 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 too 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 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.
C#
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.

C#
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

C#
//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.
C#
//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).

C#
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.

C#
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

C#
//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 its 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 instances 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 code-smell 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:

C#
// **** 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:

C#
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 and 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

C#
//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 easily 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.

C#
// *************** 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), let's 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 :-)

C#
// *************** 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

C#
//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 straightforward making a copy and then replacing Session with Application, and then inheriting 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 choose to create different base classes each using their flavor of the wrapper, and then subclasses can, in turn, in their declarations, pick whichever is suitable. The caveat here is that you'd need to duplicate all your BaseClass code to have the choice between wrappers at runtime. Not ideal, so pick your poison.

One last "embellishment" one could add, and I'll leave that as a reader's 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.

Addendum - Full blown adaptive persist wrapper; Deluxe edition.

All Addendum code examples are in the new SessionWrapperAddaptive project, along with an .aspx showing some simple test of functionality. Note that this is an individual download file.

Spurred on by the discussion with cd_dilo in the message thread, I set out to construct a full blown adaptive persist wrapper - where objects can pick whatever (available) persist medium they wish. This turned out to be not quite as straightforward as I had envisioned - primarily due to some additional constraints I imposed on myself as I went along.

My first approach (not shown here) was to simply add a simple StorageLocation field to the code of "The Code Take #3" (as done in "The Code Take #1 - Combo") and take it from there. That however quickly turned out to be not that great of an idea, as it would require to duplicate each of the base class constructors to accommodate that extra parameter.

Also, one of the constraints I ended up putting on myself was that you (in the "deluxe" version) should not need to modify any internals of the base class (for ease of use, etc.). So the idea of an extra field, requiring to duplicate the base class constructors, was quickly dismissed.

The next version ("Turtles all the way down" as I refer to it...) fulfills the requirement of not messing with the internals of the base class - but even that one can be improved; however, not without some drawbacks, which is why I've chosen to show both approaches so you can pick whichever suits you best.

Turtles all the way down

C#
public interface IPersister<T> {
    void Persist(T TInstance);
    T GetPersisted { get; }
}

/* ----------- Persisters --------------- */
public class PersistToSession<T> : IPersister<T> where T : class {
    private static string key = typeof(T).ToString();
    public void Persist(T TInstanse)
      { HttpContext.Current.Session[key] = TInstanse; }
    public T GetPersisted { get
      { return HttpContext.Current.Session[key] as T; } }
}

public class PersistToApplication<T> : IPersister<T> where T : class {
    private static string key = typeof(T).ToString();
    public void Persist(T TInstance)
      { HttpContext.Current.Application[key] = TInstance; }
    public T GetPersisted { get 
      { return HttpContext.Current.Application[key] as T; } }
}

public class DoNotPersist<T> : IPersister<T> where T : class {
    public void Persist(T TInstance) { }
    public T GetPersisted { get { return null; } }
}

/* ----------- Persist wrapper --------------- */
public abstract class PersistFactory<T, TPersister> where T : 
       class where TPersister : IPersister<T>, new() {
    private TPersister persister = new TPersister();
    public PersistFactory() { persister.Persist(this as T); }
    public T GetPersisted { get { return persister.GetPersisted; } }
}

/* ----------- BaseClasses --------------- */
public class BaseClass1<T, TPersister> : PersistFactory<T, 
       TPersister> where T : class where TPersister : IPersister<T>, new() {
    public int x { get; set; }
    public int y { get; set; }
    public int z { get; set; }
}

public class BaseClass2<T, TPersister> : PersistFactory<T, 
       TPersister> where T : class where TPersister : IPersister<T>, new() {
    public string a { get; set; }
    public string b { get; set; }
    public string c { get; set; }

    public BaseClass2(string c) { this.c = c; }
}

/* ----------- SubClasses --------------- */
public class SubClassS1 : BaseClass1<SubClassS1, PersistToSession<SubClassS1>> { }
public class SubClassS2 : BaseClass1<SubClassS2, PersistToSession<SubClassS2>> { }
public class SubClassA1 : BaseClass2<SubClassA1, 
       PersistToApplication<SubClassA1>> { 
       public SubClassA1(string s) : base(s) { } }
public class SubClassA2 : BaseClass2<SubClassA2, 
       PersistToApplication<SubClassA2>> { 
       public SubClassA2(string s) : base(s) { } }

Using the code

Sample test page:

C#
protected void Page_Load(object sender, EventArgs e) {
      SubClassS1 s1A = new SubClassS1 { x = 1 };
  SubClassS2 s2A = new SubClassS2 { x = 2 };
  SubClassA1 a1A = new SubClassA1("333");
  SubClassA2 a2A = new SubClassA2("444") { a = "4" };

  SubClassS1 s1B = s1A;
  SubClassS2 s2B = s2A;
  SubClassA1 a1B = a1A;
  SubClassA2 a2B = a2A;

  s1B.y = 11;
  s1B.z = 111;
  s2B.y = 22;
  s2B.z = 222;
  a1B.a = "3";
  a1B.b = "33";
  a2B.b = "44";

  div1.InnerHtml +=
      "s1B.x : " + s1B.x + "<br />" + "s1B.y : " + s1B.y + 
      "<br />" + "s1B.z : " + s1B.z + "<br />" +
      "s2B.x : " + s2B.x + "<br />" + "s2B.y : " + 
      s2B.y + "<br />" + "s2B.z : " + s2B.z + "<br />" +
      "a1B.a : " + a1B.a + "<br />" + "a1B.b : " + 
      a1B.b + "<br />" + "a1B.c : " + a1B.c + "<br />" +
      "a2B.a : " + a2B.a + "<br />" + "a2B.b : " + a2B.b + 
      "<br />" + "a2B.c : " + a2B.c + "<br />";
}

The output:

s1B.x : 1
s1B.y : 11
s1B.z : 111
s2B.x : 2
s2B.y : 22
s2B.z : 222
a1B.a : 3
a1B.b : 33
a1B.c : 333
a2B.a : 4
a2B.b : 44
a2B.c : 444

Some thoughts on the code

Aside: I really wish it was possible for the Generics to be able to "extract" the "inner type" of a "nested generic type" (like PersistToSession<SubClassS1>>), so it wouldn't be necessary to pass along SubClassS1 individually as well. So you instead could do something like this:

C#
//This code DOES NOT compile - Example only
//"T" becomming the <SubClassS1> type in this example

public class BaseClass1<TPersister<T>> : 
       PersistFactory<TPersister<T>> where T : 
       class where TPersister : IPersister<T>, new() {

public class SubClassS1 : BaseClass1<PersistToSession<SubClassS1>> { }

Would save some repetitive typing - but alas, it's not possible. End aside

The main change from "The Code Take #3" is that the (persist) wrapper has been turned into a factory, instead of "hard-coding" each instance of a persist medium (Session/Application/etc.), and an interface has been added, which each persister implements, to accommodate the Factory pattern.

An extra generic type parameter has been added which is the means by which you indicate the type of persister you wish to use - and this thus replaces the need for the aforementioned StorageLocation field (and associated duplication of base class constructors).

Note that if you just need to persist one instance of your base class, you can do so directly (new BaseClass1<BaseClass1, PersistToSession<BaseClass1>>()), you only need to create a subclass (an extra key) if you need to store two or more instances (this is also true for the code of "The Code Take #3).

Also note the "need" for a DoNotPersist (non)persister. If you need an object that you don't need persisted (and thus overwrite any object of that type that is persisted), DoNotPersist has to be indicated. This is only really relevant with regards to the direct use of base class objects (because if you make subclasses, it's because you want to persist). It also gives a tiny performance boost not persisting when you new() up a base class. But I think your code might have other issues if that ever became a problem :-)

So far so good. However, to really make this pattern useful across the board, the Generics specification added to the base class is a problem. If you want to use this persist pattern with other objects than your own (where you can edit in the Generics specification), that has to go - somehow... Not to mention the fact that simply creating new instances of your base class becomes a wee bit cumbersome now. Even if you just want a plain base object that you don't want to persist, you still have to add all the Generics syntax each time. Not ideal in my opinion. So I set out to get rid of that as well...

Extending what we already got

C#
public interface IPersister<T> {
    void PersistIt(T TInstance);
    T Persisted { get; }
}

/* ----------- Persisters --------------- */
public class PersistToSession<T> : IPersister<T> where T : class {
    private static string key = typeof(T).ToString();
    public void PersistIt(T TInstance) { 
      HttpContext.Current.Session[key] = TInstance; }
    public T Persisted { get { 
      return HttpContext.Current.Session[key] as T; } }
}

public class PersistToApplication<T> : IPersister<T> where T : class {
    private static string key = typeof(T).ToString();
    public void PersistIt(T TInstance) { 
      HttpContext.Current.Application[key] = TInstance; }
    public T Persisted { get { 
      return HttpContext.Current.Application[key] as T; } }
}

/* ----------- Persist wrapper (extension methods) --------------- */
public static class PersistFactory {
    public static void Persist<T, TPersister>(this T This) 
           where TPersister :  IPersister<T>, new() {
      TPersister persister = new TPersister();
      persister.PersistIt(This);
}

public static T GetPersisted<T, TPersister>(this T This) 
                where TPersister : IPersister<T>, new() {
      TPersister persister = new TPersister();
      return persister.Persisted;
    }
}

/* ----------- BaseClasses --------------- */
public class BaseClass1 {
    public int x { get; set; }
    public int y { get; set; }
    public int z { get; set; }
}

public class BaseClass2 {
    public string a { get; set; }
    public string b { get; set; }
    public string c { get; set; }

    public BaseClass2(string c) { this.c = c; }
}

/* ----------- SubClasses --------------- */
public class SubClassS1 : BaseClass1 { }
public class SubClassS2 : BaseClass1 { }
public class SubClassA1 : BaseClass2 {  
  public SubClassA1(string s) : base(s) { } }
public class SubClassA2 : BaseClass2 {  
  public SubClassA2(string s) : base(s) { } }

Using the code

Sample test page:

C#
using s1 = PersistToSession<SubClassS1>;
using s2 = PersistToSession<SubClassS2>;
using a3 = PersistToApplication<SubClassA1>;
using a4 = PersistToApplication<SubClassA2>;

public partial class SessionWrapperAdaptive2 : System.Web.UI.Page {
    protected void Page_Load(object sender, EventArgs e) {
      SubClassS1 s1A = new SubClassS1 { x = 1 };
      SubClassS2 s2A = new SubClassS2 { x = 2 };
      SubClassA1 a1A = new SubClassA1("333");
      SubClassA2 a2A = new SubClassA2("444") { a = "4" };

      //Need to manually persist
      //s1A.Persist<SubClassS1, PersistToSession<SubClassS1>>();
      s1A.Persist<SubClassS1, s1>();
      //s2A.Persist<SubClassS2, PersistToSession<SubClassS2>>();
      s2A.Persist<SubClassS2, s2>();
      //a1A.Persist<SubClassA1, PersistToApplication<SubClassA1>>();
      a1A.Persist<SubClassA1, a3>();
      //a2A.Persist<SubClassA2, PersistToApplication<SubClassA2>>();
      a2A.Persist<SubClassA2, a4>();

      SubClassS1 s1B = s1A.GetPersisted<SubClassS1, s1>();
      SubClassS2 s2B = s2A.GetPersisted<SubClassS2, s2>();
      SubClassA1 a1B = a1A.GetPersisted<SubClassA1, a3>();
      SubClassA2 a2B = a2A.GetPersisted<SubClassA2, a4>();

      s1B.y = 11;
      s1B.z = 111;
      s2B.y = 22;
      s2B.z = 222;
      a1B.a = "3";
      a1B.b = "33";
      a2B.b = "44";

      div1.InnerHtml +=
          "s1B.x : " + s1B.x + "<br />" + "s1B.y : " + 
          s1B.y + "<br />" + "s1B.z : " + s1B.z + "<br />" +
          "s2B.x : " + s2B.x + "<br />" + "s2B.y : " + 
          s2B.y + "<br />" + "s2B.z : " + s2B.z + "<br />" +
          "a1B.a : " + a1B.a + "<br />" + "a1B.b : " + 
          a1B.b + "<br />" + "a1B.c : " + a1B.c + "<br />" +
          "a2B.a : " + a2B.a + "<br />" + "a2B.b : " + 
          a2B.b + "<br />" + "a2B.c : " + a2B.c + "<br />";
    }
}

The output of the sample test page is identical to the prior output

Some thoughts on the code

First, notice that the base classes are now completely untouched. As per my self inflicted requirement. So mission accomplished.

To accomplish this, (generic) Extension Methods where used, which further means that the methods are now added to all objects, including .NET objects, thus we can now persist any object we want.

Also notice that the DoNotPersist (non)persister is gone - it's no longer needed, as objects are no longer persisted by default - which is the reverse of what we started out with.

The persist factory class has been turned into a static class (required) containing (generic) static extension methods, static once more required. As it's a static class, we can't reuse an instance of TPersister persister = new TPersister(); from the class itself, it has to be instantiated each time in the (extension) methods instead (tiny performance hit there).

Also, our trusty getter Stored has to be turned into an actual method (no such thing as extension properties unfortunately); as such, I've given it a name change (to GetStored()). That's really a minor nibble though.

One downside is that we now have to tell an object to persist itself (i.e., s1A.Persist<SubClassS1, s1>();) where before it happened automatically whenever we new()'ed up a new object (I liked that feature...). Further, it takes quite a bit of typing (even with intellisense) to use these generic extension methods - so I've employed some "alias"'ing as well :-). In tandem, it's equally cumbersome calling GetStored now.

On balance, I guess the last version is the most re-useable of the lot, despite its drawbacks. Although I do miss the "automagic" features... and ease of use (typing)...

Can't win them all I guess :-)

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.
  • 2011-03-17: Addendum section added: Containing two versions of a full blown adaptive persist wrapper ("Deluxe version") as discussed in the message thread. Separate file download for the new source code added (SessionWrapperAddaptive).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)