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.
public class MyClass {
public int x { get; set; }
public int y { get; set; }
public int z { get; set; }
}
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; }
public MyClassWrappedv1() {
HttpContext.Current.Session[key] = this;
}
public static MyClassWrappedv1 Stored {
get { return HttpContext.Current.Session[key] as MyClassWrappedv1; }
}
}
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
MyClassWrappedv1 MyWrappedv1A = new MyClassWrappedv1 { x = 1 };
Note the use of the nifty Object Initializer feature - keeps things nice and terse.
MyClassWrappedv1 MyWrappedv1A = MyClassWrappedv1.Stored;
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/els
e 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; }
public MyClassWrappedCombov1(StorageLocation sl) {
if (sl == StorageLocation.Session) {
HttpContext.Current.Session[key] = this;
location = StorageLocation.Session;
}
else {
HttpContext.Current.Application[key] = this;
location = StorageLocation.Session;
}
}
public static MyClassWrappedCombov1 Stored {
get {
if (location == StorageLocation.Session) {
return HttpContext.Current.Session[key] as MyClassWrappedCombov1;
}
else {
return HttpContext.Current.Application[key] as MyClassWrappedCombov1;
}
}
}
}
Using the code
MyClassWrappedCombov1 MyWrappedCombov1A =
new MyClassWrappedCombov1(StorageLocation.Session) { x = 5 };
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:
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; }
public MyBaseClass() {
HttpContext.Current.Session[key] = this;
}
public static MyBaseClass Stored {
get { return HttpContext.Current.Session[key] as MyBaseClass; }
}
}
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; }
public MyBaseClass() {
HttpContext.Current.Session[key] = this;
}
public static T Stored {
get { return HttpContext.Current.Session[key] as T; }
}
}
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
MyClass1 mc1 = new MyClass1 { x = 1 };
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.
public class SessionWrapper<T> where T : class {
private static string sessionvar = typeof(T).ToString();
public SessionWrapper() {
HttpContext.Current.Session[sessionvar] = this;
}
public static T Stored {
get { return HttpContext.Current.Session[sessionvar] as T; }
}
}
public class MyBaseClass<T> : SessionWrapper<T> where T : class {
public int x { get; set; }
public int y { get; set; }
public int z { get; set; }
}
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 :-)
public class MyOtherBaseClass<T> : SessionWrapper<T> where T : class {
public string a { get; set; }
public string b { get; set; }
public string c { get; set; }
public MyOtherBaseClass(string s) {
this.c = s;
}
}
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
MyClass1 mc1A = new MyClass1 { x = 1 };
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
public interface IPersister<T> {
void Persist(T TInstance);
T GetPersisted { get; }
}
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; } }
}
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; } }
}
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; }
}
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:
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:
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
public interface IPersister<T> {
void PersistIt(T TInstance);
T Persisted { get; }
}
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; } }
}
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;
}
}
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; }
}
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:
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" };
s1A.Persist<SubClassS1, s1>();
s2A.Persist<SubClassS2, s2>();
a1A.Persist<SubClassA1, a3>();
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).