ASP.NET OO SessionWrapper - 作为对象本身的一个组成部分
将 ASP.NET OO Session 包装器嵌入到对象本身,而不是一个包含所有要持久化的对象的单一包装器。
引言
Session 包装器 - 几乎不需要介绍 :-)
Session 包装器旨在解决(至少)两个方面的问题
- 在代码中避免使用“魔术字符串”(用于 `Session` 对象引用的键)
- 它们很容易拼写错误 - 例如拼写、大小写等。更不用说,它们会迅速散布在各处,没有人会知道有多少个实际上在使用中。这太糟糕了。
- 为存储在 `Session` 对象中的对象实现智能感知。
- 为存储在 `Session` 对象中的对象实现智能感知,可以消除上述许多问题,同时提高每个人的效率。它还告诉大家,首先“可以”(允许)将什么放入 `Session` 对象中。
在接下来的文章中,我将从一个非常简单的例子开始,然后慢慢将其构建得更通用,从而更有用,并且更容易与要包装的对象一起部署。
背景
撰写本文的动力是我潜在项目的一个 Yet-Another-Session-Wrapper 的需求。
已经“经历过”几次了,但每次都是那种将所有东西简单地放入一个自定义类中的单一 Session 包装器。
这次我想做得有点不同,因为那种方法一直让我不太舒服。对象如何存储自身应该是对象本身的属性;也就是说,我有一个对象 A,所以我告诉 A(以某种方式)存储它自己,而不是去寻找为此应用程序构建的任何自定义实用保存工具。对我来说,这只是更好的 OO 编码逻辑(YMMV)。
你可以跳过下一节。真的。我只是在 rambling,解释你为什么一开始要看这个。
所以,我快速地进行了一些 Google 搜索,看看其他人是否已经想出了我能 偷 使用的东西(嘿,我是程序员,我很懒 :-))。现在,要创建一个单一的 Session 包装器非常容易,所以显然我找到的几乎都是这个。有趣的是,即使是如此简单的概念也可以以多种方式设计,每种都有其优点和缺点。它涵盖了从简单地为 Session 包装器自定义对象添加一些自定义属性,到在更高级的版本中使用 Singleton 模式和泛型。
然而,它们都具有一个共同点,那就是它们是简单的单一的“野兽”,在我看来,它们将良好的 OO 抛在了脑后。这正是我不想要的。
最后,我只找到一个(我定义的)OO Session Wrapper 的例子,而且那个例子至少可以说很糟糕。代码中有太多“代码异味”(方法只包含调用另一个方法的单行代码,而且没有参数可以产生区别)。
我曾考虑过链接它,但那可能不公平。至少这个人对解决问题的方法有正确的想法,而且他付出了努力发布它,即一个OO Session Wrapper。
现在,可能有一些很好的例子是我想要的,但我的 Google 搜索没有找到。所以,“是时候自己动手了”。
代码 - 第一版
所有第一版代码示例都在 SessionWrapperSimple 项目中,以及一个 `.aspx` 文件,展示了一些简单的功能测试。
public class MyClass {
public int x { get; set; }
public int y { get; set; }
public int z { get; set; }
// *** Various MyClass Methods below ***
}
这是我们的起点。
请注意使用了漂亮的 自动属性 功能 - 使代码更简洁。
所以 - 我们有一个类,我们想将这个类的对象存储在 `Session` 中。下面是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 ***
}
如果您只需要一个快速粗糙的解决方案,您可以到此为止。只需粘贴几行代码,就可以了。
使用代码
//Instantiate a new object - Same as you'd always do.
MyClassWrappedv1 MyWrappedv1A = new MyClassWrappedv1 { x = 1 };
请注意使用了漂亮的 对象初始化程序 功能 - 使代码更简洁。
//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;
与单一 Session 包装器一样,使用OO Session Wrapper,您也需要事先确定是否要“Session 包装”给定类型的对象 - 这不是一个完全“免费的午餐”。
在简单的单一 Session 包装器中,您将添加一个对象类类型的字段,然后添加相应的 getter 和 setter。
使用OO Session Wrapper,您将上述代码直接添加到您的对象类中(替换为正确的类名),然后就可以开始使用了。非常简单直接。
第一版代码 - 组合
上述简单示例的一个(多个)“不足之处”在于它仅用于 `Session`。当然,将 `Session` 替换为 `Application` 以使用 `Application` 对象是微不足道的。
它确实指出了一个限制 - 您需要在设计时决定在哪里存储您的对象。
接下来的代码允许您在运行时决定在哪里存储您的对象(`Session` 或 `Application`)。但它必须是两者之一。如果您“改变主意”,例如从 `Session` 更改为 `Application`(当您创建一个新对象时),您将丢失存储在 `Session` 中的对象的隐式对象引用(当然,您仍然可以有一个显式的变量引用)。
首先,我们想要一个枚举,用于区分 `Session` 和 `Application`(再次避免在我们的代码中使用任何“魔术字符串”)。
public enum StorageLocation {
Session,
Application
}
管理对象是进入 `Session` 还是 `Application` 的代码当然是一个简单的 `if/else` 结构,基于静态位置字段。
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 ***
}
使用代码
//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;
关注点
到目前为止,我们还没有做任何特别有趣的事情。一切都相当简单直接,但代码就在那里,因此您可以复制/粘贴,而无需下载所有内容,如果您只需要一些简单的事情。
一些观察
我们已经实现了我们想要完成的目标
- 从我们的通用代码中删除“魔术字符串”。
- 为我们存储的对象实现智能感知(它是隐式的,因为包装器已嵌入到对象中)。
值得一提的是,将其从内存存储扩展到其他类型(文件/数据库等)是相当容易的 - 尽管以其当前形式,除非有非常特殊的需求,否则我不推荐这样做。
OO Session Wrapper 实现的一个优点是,从 `Session` 中提取对象(它将所有内容存储为 `Object`)的类型转换也是在类型本身内部处理的,而单一 Session 包装器必须处理所有类型的对象(非常简单,但不如这个好)。
因此,我们完成了OO Session Wrapper。除了还有一些...
问题
我之前提到过,当前的代码存在一些“不足之处”。一个小麻烦是对象的键仍然是“手动完成”的,尽管在类内部,因此在我们的通用代码中不可见。
这可以通过在赋值中使用 `typeof(MyClass)` 来轻松解决,这将在下面完成。我将其保留为文字字符串只是为了演示目的。
一个主要明显的问题是,如果我们想持久化同一类型的两个实例,我们就遇到了麻烦 - 我们无法做到。创建新对象会自动覆盖先前持久化的对象(同一类型)。
下一节将探讨解决该问题的方法。
第二版代码
所有第二版代码示例都在 SessionWrapperBase 项目中,以及一个 `.aspx` 文件,展示了一个最小化的使用测试。
无法保存同一类型的两个实例的简单解决方案是简单地复制粘贴类定义并给它一个略微不同的名称。完成。
这当然会导致大量相同的代码。特别是如果我们想同时持久化 3, 4, 5 等对象。如果我见过代码异味,那肯定就是这个。
当然,子类化来救援。
选择上面适合您的口味和需求的任何代码示例,然后将类重命名为 `MyBaseClass` 或类似名称 - 然后进行子类化。
类似这样的东西
// **** 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 { };
正如第一行所说 - 这样做行不通。
两个问题:首先,每个子类将接收/使用相同的 `key` - 因此,我们一无所获。其次,`Stored` 的返回类型是 `MyBaseClass`,您不能将其分配给子类,即 `MyClass1 mc1 = MyBaseClass.Stored` 行不通。而 `MyBaseClass` 当然完全不知道当前使用的是哪个子类。
要是有一种方法......能够将子类的类型“传递”给基类,最好是作为变量(因为它会变化)以某种方式......
碰巧,这几乎就是(相当宽松的 :-)) 泛型 的描述。如果您不熟悉泛型,请遵循以上链接。它和任何其他入门资料一样好。
通过一点泛型魔法,我们可以快速将上述代码转换为以下代码
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> { };
这确实不是什么大事,现在它完全符合我们的要求。`key` 对每个子类都是唯一的,因此它们不会互相覆盖。此外,每个子类都是空的,因此如果需要添加新的子类,它非常快速和容易。
事实上,它现在需要的代码比单一 Session 包装器还要少,后者需要属性声明和相应的 getter/setter。
在 `Stored` 中使用 `T` 的类型转换,所以需要 "where T : class
"。
使用代码
//Instantiation hasn't changed.
MyClass1 mc1 = new MyClass1 { x = 1 };
//Nor has anything else
mc1 = MyClass1.Stored;
mc1.x = 2;
当然,您可以相当容易地将上述代码扩展到使用第一版代码中展示的(太多)组合。我不会再包含所有这些了 :-)
我们还没有完全完成 - 还有一个改进要做 :-)
第三版(也是最后一版)代码
所有第三版代码示例都在 SessionWrapper 项目中,以及一个 `.aspx` 文件,展示了一个最小化的使用测试。
唯一需要改进的是代码的可重用性。目前,我们需要将 Session 包装器代码复制并粘贴到我们要持久化的每个对象的类中。这确实不是很多代码行 - 但你知道,有一天你可能会改变主意,你想在哪里持久化你的对象...
为了实现更高级别的解耦和可重用性,我们将再次进行子类化。诀窍在于将对象初始化代码和其他方法与实际的 Session 包装器代码隔离开来。
// *************** 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> { };
为了展示可重用性(无需更改 Session 包装器代码),让我们再创建一个类,这次它甚至接受一个构造函数参数 - 这确实会给子类声明代码增加一点工作量,但它是可管理的 - 即使是相当长的类名,它仍然是单行的 :-)
// *************** 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) { } };
`MyClassN` 和 `MyOtherClassN` 都将正常工作,无需更改 Session 包装器。因此,Session 包装器现在可以安全地存放在代码库的某个地方,并且再也不会被触及。
使用代码
//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" };
结束语
上面只展示了 Session 对象的包装器,但复制它然后用 `Application` 替换 `Session`,然后从中继承是很容易的。其他包装器可能需要更专业的代码(保存到文件/数据库等),但它们同样容易插入到您需要它们的地方。您甚至可以选择创建不同的基类,每个基类使用其特定类型的包装器,然后子类可以根据其声明,选择合适的包装器。这里的警告是,您需要复制所有 `BaseClass` 代码才能在运行时选择包装器。不理想,所以选择您喜欢的。
最后一个可以添加的“润色”是添加一个开关,用于判断您在 `new()` 一个对象时是否希望它被持久化 - 我认为这会非常有用 :-)
总的来说,我认为最终呈现的设计非常灵活和强大,即使所需的编码量极少。我知道当我最终完成它时*我*很惊讶。它如此......简单而优雅(在我看来):-)
事实上,我也很惊讶我没有在外面找到其他类似的东西 - 这将为我节省大量时间来写这篇文章!
享受。
附录 - 全功能的自适应持久化包装器;Deluxe 版。
所有附录代码示例都在新的 SessionWrapperAddaptive 项目中,以及一个 `.aspx` 文件,展示了一些简单的功能测试。请注意,这是一个单独的下载文件。
受消息线程中与 cd_dilo 的讨论启发,我开始构建一个全功能的自适应持久化包装器 - 对象可以选择任何(可用的)持久化介质。事实证明这并不像我最初设想的那么简单 - 主要原因是我在进行过程中给自己施加了一些额外的限制。
我的第一个方法(此处未显示)是简单地将一个简单的 `StorageLocation` 字段添加到“第三版代码”的代码中(如“第一版代码 - 组合”中所做的),然后以此为基础。然而,事实证明这并不是一个好主意,因为它需要复制每个基类构造函数来容纳那个额外的参数。
另外,我最终给自己施加的一个限制是,您(在“Deluxe”版本中)不应该需要修改基类的任何内部内容(为了易于使用等)。因此,复制基类构造函数的额外字段的想法很快就被否决了。
下一个版本(我称之为“层层叠叠的乌龟”)满足了不修改基类内部内容的要求 - 但即使是那个版本也可以改进;然而,并非没有一些缺点,这就是为什么我选择展示这两种方法,以便您可以选择最适合您的方法。
层层叠叠的乌龟
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) { } }
使用代码
示例测试页面
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 />";
}
输出
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
关于代码的一些思考
附注:我真的很希望泛型能够“提取”一个“嵌套泛型类型”的“内部类型”(例如 `PersistToSession<SubClassS1>>`),这样就不必单独传递 `SubClassS1` 了。这样您就可以这样做
//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>> { }
可以节省一些重复的输入 - 但唉,这不可能。结束附注
与“第三版代码”相比,主要的变化是(持久化)包装器已转变为一个工厂,而不是“硬编码”每个持久化介质(`Session` / `Application` / 等)的实例,并且添加了一个接口,每个持久化器都实现该接口,以适应工厂模式。
添加了一个额外的泛型类型参数,这是您指示要使用的持久化器类型的手段 - 这取代了对上述 `StorageLocation` 字段(以及相关的基类构造函数复制)的需要。
请注意,如果您只需要持久化一个基类实例,您可以直接这样做(`new BaseClass1<BaseClass1, PersistToSession<BaseClass1>>()`),如果您需要存储两个或更多实例,您才需要创建一个子类(额外的 `key`)(这对于“第三版代码”也是如此)。
另请注意 `DoNotPersist`(非)持久化器的“需要”。如果您需要一个不需要持久化的对象(从而覆盖任何已持久化的同类型对象),则必须指明 `DoNotPersist`。这只与直接使用基类对象有关(因为如果您创建子类,那是因为您想持久化)。它还可以在您 `new()` 基类对象时提供一点性能提升,因为它不持久化。但我认为如果那成为一个问题,您的代码可能会有其他问题 :-)
到目前为止一切顺利。然而,要使此模式真正具有普遍性,添加到基类中的泛型规范是一个问题。如果您想将此持久化模式用于您自己的对象以外的其他对象(您可以在其中编辑泛型规范),则必须删除它......不知何故......更不用说仅仅创建您的基类的新实例现在变得有些笨拙了。即使您只是想要一个您不想持久化的普通基类对象,每次都必须添加所有泛型语法。我认为这并不理想。所以我开始着手解决这个问题...
扩展我们已有的
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 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 />";
}
}
示例测试页面的输出与之前的输出相同
关于代码的一些思考
首先,请注意基类完全未被触动。符合我自我设定的要求。所以任务完成了。
为了实现这一点,使用了(泛型)扩展方法,这意味着现在可以将方法添加到所有对象,包括 .NET 对象,因此我们可以持久化任何我们想要的对象。
另外请注意 `DoNotPersist`(非)持久化器已消失 - 它不再需要了,因为对象默认不再被持久化 - 这与我们最初的情况相反。
持久化工厂类已转换为一个 `static` 类(必需),其中包含(泛型)`static` 扩展方法,再次是 `static`(必需)。由于它是一个静态类,我们无法重用该类本身的 `TPersister persister = new TPersister();` 实例,它必须在(扩展)方法中每次实例化(那里有一个微小的性能损失)。
另外,我们值得信赖的 getter `Stored` 必须转换为一个实际的方法(可惜没有扩展属性);因此,我将其重命名为(`GetStored()`)。但这只是一个小小的不足。
一个缺点是我们现在必须告诉对象持久化它自己(即 `s1A.Persist<SubClassS1, s1>();`),而以前当我们 `new()` 一个新对象时,它会自动发生(我喜欢这个功能...)。此外,使用这些泛型扩展方法需要输入很多内容(即使有智能感知)- 因此我也使用了一些“别名”。同时,现在调用 `GetStored` 也同样麻烦。
总而言之,我认为最后一个版本是所有版本中最可重用的,尽管存在缺点。尽管我很怀念“自动魔术”功能......以及易用性(输入)...
我想我不能什么都得到 :-)
尽情享用!
历史
- 版本 1:2011-03-14。
- 代码第一版 - 组合:`Stored` v2 将 `Session` 复制了两次,而不是 `Session` / `Application`(根据需要更新您的源文件 - 尽管您真的不应该使用这种方法)。
- 2011-03-16:已删除对(最终有缺陷的)`Stored` 版本 2 的所有引用。有关更多信息,请参阅消息。
- 2011-03-17:添加了附录部分:包含两个版本的全功能自适应持久化包装器(“Deluxe 版本”),如消息线程中所讨论。已为新源代码添加单独的文件下载(SessionWrapperAddaptive)。