相信大家對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.