Context+ContextScope——这是否可以看作一种设计模式?
相信大家对TransactionScope都比较熟悉。通过TransactionScope,我们可以很容易地将一组操作纳入同一个事务中;或者说TransactionScope创建一个基于事务的上下文范围,在这个范围内共享一个相同的环境事务(Ambient Transaction)。我个人觉得这体现了一种可以重用的模式,即本篇文章介绍的Context+ContextScope模式,这种模式旨在一定范围内创建一个可以共享的上下文信息。
我们通过一个简单的例子来对Context+ContextScope模式进行简单说明。比如在一个安全级别比较高的GUI应用中,我们需要对用户的每一个UI操作进行安全审核(Auditing,比如记录下当前操作的意图、操作时间和用户名等),我们将这个被审核的UI操作称为“活动(Activity)”。如果我们能够将针对这个UI操作的执行(事件的触发、对业务逻辑的执行以及对数据库的访问)纳入同一个基于活动的上下文中,那么审核就可以直接从当前的环境上下文中获取到相应的审核信息了。[源代码从这里下载]
一、ActivityContext
为此,我们创建了如下一个名为ActivityContext的类型表示针对Activity的上下文。ActivityContext具有三个实例属性,其中ActivityName和DateTime表示活动的名称和开始时间,而字典类型的Properties 属性用于维护Activity相关的额外属性。静态Current属性表示当前的环境上下文(Ambient Context),它返回的是静态字段current。值得一提的,在该静态字段上应用了ThreadStaticAttribute特性,意味着静态字段仅仅限于当前的线程。这也说明了Context+ContextScope模式主要应用于同步环境,如果需要对异步环境进行支持,可以做一些额外处理。
1: public class ActivityContext: IDisposable
2: {
3: [ThreadStatic]
4: private static ActivityContext current;
5:
6: public string ActivityName { get; private set; }
7: public DateTime StartTime { get; private set; }
8: public IDictionary<string, object> Properties { get; private set; }
9:
10: internal ActivityContext(string activityName)
11: {
12: this.ActivityName = activityName;
13: this.StartTime = DateTime.Now;
14: this.Properties = new Dictionary<string, object>();
15: }
16:
17: public static ActivityContext Current
18: {
19: get { return current; }
20: internal set{current = value;}
21: }
22:
23: public void Dispose()
24: {
25: foreach (var property in this.Properties.Values)
26: {
27: IDisposable disposable = property as IDisposable;
28: if (null != disposable)
29: {
30: disposable.Dispose();
31: }
32: }
33: }
34: }
二、ActivityContextScope
Context+ContextScope的核心不在于Context而在于ContextScope,即我们需要控制上下文的范围。对于我们的安全审核场景来说,我们需要针对用于的UI操作(比如点击某个按钮)创建ActivityContext,该上下文的生命周期仅限于针对UI事件的响应过程。为此我们创建一个ActivityContextScope类型用于创建ActivityContext并控制其生命周期。如下面的代码片断所示,ActivityContextScope除了接受一个表示创建活动的名称之外,还具有一个ContextScopeOperation枚举类型的参数。
1: public class ActivityContextScope: IDisposable
2: {
3: private ActivityContext current = ActivityContext.Current;
4: private ActivityContext newContext;
5:
6: public ActivityContextScope(string activityName, ContextScopeOption contextScopeOption = ContextScopeOption.Required)
7: {
8: switch (contextScopeOption)
9: {
10: case ContextScopeOption.Required:
11: {
12: if (null == current)
13: {
14: ActivityContext.Current = newContext = new ActivityContext(activityName);
15: }
16: break;
17: }
18: case ContextScopeOption.RequiresNew:
19: {
20: ActivityContext.Current = newContext = new ActivityContext(activityName);
21: break;
22: }
23: case ContextScopeOption.Suppress:
24: {
25: ActivityContext.Current = null;
26: break;
27: }
28: }
29: }
30:
31: public void Dispose()
32: {
33: ActivityContext.Current = current;
34: if (null != newContext)
35: {
36: newContext.Dispose();
37: }
38: }
39: }
40:
41: public enum ContextScopeOption
42: {
43: Required,
44: RequiresNew,
45: Suppress
46: }
考虑在创建ActivityContextScope的时候,当前环境上下文可能已经存在,那么是重用现成的上下文还是创建新的上下文,可以通过ContextScopeOperation枚举来控制。该枚举类型的Required和RequiredNew选项分别表示重用现有上下文和创建新的上下文。另一个选项Supress表示创建一个“无环境上下文”的范围,比如TransactionScope通过类似的机制将不需要纳入事务的操作(比如Logging)从环境事务中剥离出来。基于ContextScopeOperation的ActivityContext创建机制体现在ActivityContextScope的构造函数中。ActivityContextScope实现了IDisposable接口,在实现的Dispose方法中我们将通过ActivityContext的静态属性Current表示的环境上下文恢复到ActivityContextScope创建之前的状态。
三、ActivityContextScope的使用
我们通过如下一个简单的实例来演示ActivityContextScope的使用。在Main方法中我们在一个基于“Activty1”的ActivityContextScope中调用Activty1方法。在Activty1方法中,我们在一个基于“Activty2”的ActivityContextScope中调用Activty2方法。两次创建ActivityContextScope都采用默认的ContextScopeOperation(Required)。在方法Activty2中,我们在一个基于“Activty3”的ActivityContextScope中调用Activty3方法,创建ActivityContextScope时选择RequiredNew选项。而在Activty3方法中,我们针对Supress选项创建ActivityContextScope并调用Activity4方法。方法Activty1、Activty2、Activty3和Activty4中均调用DisplayCurrentContext将当前的ActivityContext信息打印出来。
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: using (ActivityContextScope contextScope = new ActivityContextScope("Activty1"))
6: {
7: Activty1();
8: }
9: }
10:
11: static void DisplayCurrentContext(string methodName)
12: {
13: if (null != ActivityContext.Current)
14: {
15: Console.WriteLine("{0}: Current ambient activity is {1}", methodName, ActivityContext.Current.ActivityName);
16: }
17: else
18: {
19: Console.WriteLine("{0}: No ambient activity.", methodName);
20: }
21: }
22:
23: private static void Activty1()
24: {
25: DisplayCurrentContext("Activty1");
26: using (ActivityContextScope contextScope = new ActivityContextScope("Activty2"))
27: {
28: Activty2();
29: }
30: }
31:
32: private static void Activty2()
33: {
34: DisplayCurrentContext("Activty2");
35: using (ActivityContextScope contextScope = new ActivityContextScope("Activty3", ContextScopeOption.RequiresNew))
36: {
37: Activty3();
38: }
39: }
40:
41: private static void Activty3()
42: {
43: DisplayCurrentContext("Activty3");
44: using (ActivityContextScope contextScope = new ActivityContextScope("", ContextScopeOption.Suppress))
45: {
46: Activty4();
47: }
48: }
49:
50: private static void Activty4()
51: {
52: DisplayCurrentContext("Activty4");
53: }
54: }
上面这段程序执行之后会在控制台上生成如下的输出结果,我们可以看到当前环境上下文是严格按照我们创建ActivityContextScope的方式来控制的。
1: Activty1: Current ambient activity is Activty1
2: Activty2: Current ambient activity is Activty1
3: Activty3: Current ambient activity is Activty3
4: Activty4: No ambient activity.