ASP.NET MVC 開源專案Kigg解讀(1)
Kigg是一個很好的ASP.NET MVC範例專案,本著研究的目的,對Kigg進行解讀。
-
- ASP.NET MVC
- Linq To SQL
- MS Patterns & Practices – Enterprise Library (Logging & Caching)
- MS Patterns & Practices - Unity
- jQuery
- xUnit.net
- Moq
- HtmlAgilityPack
- DotNetOpenId
- jQuery UI & Markitup
Kigg介紹:
KiGG 是一個微軟技術支援部門開發的Web 2.0 風格的社會新聞軟體,採用如下的開發元件:
可以從http://kigg.codeplex.com/ 下載全部原始碼
示例站點: KiGG v2.6 Beta
一、啟動篇
就如一個作業系統,開機時需要boot,於是kigg也從boot開始!
在Kigg.Core中,定義了IBootstrapperTask介面
public interface IBootstrapperTask
{
void Execute();
}
縱觀Kigg的原始碼,我們可以發現共有4個Boot Task,如下圖所示:
這4個task分別是建立預設使用者,註冊Controller工廠,註冊路由,和啟動後臺任務(Background Tasks)
怎麼在系統啟動的時候呼叫IBootstrapperTask?Kigg專門建立了一個靜態類Bootstrapper:
1: public static class Bootstrapper 2: { 3: static Bootstrapper() 4: { 5: try 6: { 7: IoC.InitializeWith(new DependencyResolverFactory()); 8: } 9: catch (ArgumentException) 10: { 11: // Config file is Missing 12: } 13: } 14: 15: public static void Run() 16: { 17: IoC.ResolveAll<IBootstrapperTask>().ForEach(t => t.Execute()); 18: } 19: }
在該類的靜態建構函式裡,進行的是IOC(這裡用的是Unity)的初始化工作。同時,Bootstrapper類還有個Run方法,該方法呼叫IOC Resolve所有實現了IBootstrapperTask介面的任務,然後ForEach(一個擴充套件方法,遍歷集合)每個任務並Execute。
於是,我們在Kigg的GlobalApplication裡看到了華麗麗的Bootstrapper.Run();
1: public class GlobalApplication : HttpApplication
2: {
3: public static void OnStart()
4: {
5: Bootstrapper.Run();
6: Log.Info("Application Started");
7: }
8: }
二、後臺任務
其實分析IBootstrapperTask的初衷是對Kigg的後臺任務(BackgroundTask)感興趣:
1: public interface IBackgroundTask
2: {
3: bool IsRunning
4: {
5: get;
6: }
7:
8: void Start();
9:
10: void Stop();
11: }
在Kigg中,共有5種後臺任務:
這些後臺任務的開啟,是在實現了IBootstrapperTask介面的StartBackgroundTasks中開啟的:
1: public class StartBackgroundTasks : IBootstrapperTask
2: {
3: private readonly IBackgroundTask[] _tasks;
4:
5: public StartBackgroundTasks(IBackgroundTask[] tasks)
6: {
7: Check.Argument.IsNotEmpty(tasks, "tasks");
8:
9: _tasks = tasks;
10: }
11:
12: public void Execute()
13: {
14: _tasks.ForEach(t => t.Start());
15: }
16: }
StartBackgroundTasks 類的建構函式引數是通過IOC搞定的(後面會單獨介紹IOC)
三、事件聚合器IEventAggregator
在分析BackgroundTask的程式碼時,發現所有的BackgroundTask都繼承於BaseBackgroundTask:
在檢視BaseBackgroundTask時,發現了令人驚喜的東西——IEventAggregator
EventAggregator是何許玩意呢?按字面意思,事件聚合器?姑且這麼叫吧!這個介面只有一個方法:
public interface IEventAggregator
{
TEventType GetEvent<TEventType>() where TEventType : BaseEvent;
}
作用是獲取一個繼承BaseEvent的事件(Event).
為了弄清楚EventAggregator到底有什麼用,我們先來看看與BaseEvent相關的幾個類:
首先是一個事情訂閱介面,包含一個訂閱Token,一個獲取可執行函式的方法。
1: public interface IEventSubscription
2: {
3: SubscriptionToken SubscriptionToken
4: {
5: get;
6: set;
7: }
8:
9: Action<object[]> GetExecutionStrategy();
10: }
訂閱Token,實現了IEquatable介面,可以進行比較,這裡的Token沒什麼特別的作用,僅僅用來標識一個訂閱,這樣在移除訂閱的時候通過Token能方便的找到並移除
1: public class SubscriptionToken : IEquatable<SubscriptionToken>
2: {
3: private readonly Guid _token = Guid.NewGuid();
4:
5: [DebuggerStepThrough]
6: public bool Equals(SubscriptionToken other)
7: {
8: return (other != null) && Equals(_token, other._token);
9: }
10:
11: [DebuggerStepThrough]
12: public override bool Equals(object obj)
13: {
14: return ReferenceEquals(this, obj) || Equals(obj as SubscriptionToken);
15: }
16:
17: [DebuggerStepThrough]
18: public override int GetHashCode()
19: {
20: return _token.GetHashCode();
21: }
22:
23: [DebuggerStepThrough]
24: public override string ToString()
25: {
26: return _token.ToString();
27: }
28: }
再來看最為關鍵的BaseEvent
1: /// <summary>
2: /// 事件基類
3: /// </summary>
4: public abstract class BaseEvent
5: {
6: private readonly List<IEventSubscription> _subscriptions = new List<IEventSubscription>();
7:
8:
9: /// <summary>
10: /// 訂閱者
11: /// </summary>
12: protected ICollection<IEventSubscription> Subscriptions
13: {
14: [DebuggerStepThrough]
15: get
16: {
17: return _subscriptions;
18: }
19: }
20:
21: /// <summary>
22: /// 訂閱
23: /// </summary>
24: /// <param name="eventSubscription"></param>
25: /// <returns></returns>
26: protected virtual SubscriptionToken Subscribe(IEventSubscription eventSubscription)
27: {
28: eventSubscription.SubscriptionToken = new SubscriptionToken();
29:
30: lock (_subscriptions)
31: {
32: _subscriptions.Add(eventSubscription);
33: }
34:
35: return eventSubscription.SubscriptionToken;
36: }
37:
38: protected virtual void Publish(params object[] arguments)
39: {
40: List<Action<object[]>> executionStrategies = PruneAndReturnStrategies();
41:
42: foreach (var executionStrategy in executionStrategies)
43: {
44: executionStrategy(arguments);
45: }
46: }
47:
48: public virtual void Unsubscribe(SubscriptionToken token)
49: {
50: lock (_subscriptions)
51: {
52: IEventSubscription subscription = _subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token);
53:
54: if (subscription != null)
55: {
56: _subscriptions.Remove(subscription);
57: }
58: }
59: }
60:
61: public virtual bool Contains(SubscriptionToken token)
62: {
63: lock (_subscriptions)
64: {
65: IEventSubscription subscription = _subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token);
66:
67: return (subscription != null);
68: }
69: }
70:
71: private List<Action<object[]>> PruneAndReturnStrategies()
72: {
73: List<Action<object[]>> returnList = new List<Action<object[]>>();
74:
75: lock (_subscriptions)
76: {
77: for (int i = _subscriptions.Count - 1; i >= 0; i--)
78: {
79: Action<object[]> subscriptionAction = _subscriptions[i].GetExecutionStrategy();
80:
81: if (subscriptionAction == null)
82: {
83: _subscriptions.RemoveAt(i);
84: }
85: else
86: {
87: returnList.Add(subscriptionAction);
88: }
89: }
90: }
91:
92: return returnList;
93: }
94: }
BaseEvent包含了Subscribe,Publish這兩個對事件進行處理的關鍵方法:
Subscribe時會新增一個IEventSubscription,Publish時會執行所有IEventSubscription中的方法。
回過頭來,再來看BaseBackgroundTask:
1: public abstract class BaseBackgroundTask : IBackgroundTask
2: {
3: private readonly IEventAggregator _eventAggregator;
4:
5: protected BaseBackgroundTask(IEventAggregator eventAggregator)
6: {
7: Check.Argument.IsNotNull(eventAggregator, "eventAggregator");
8:
9: _eventAggregator = eventAggregator;
10: }
11:
12: public bool IsRunning
13: {
14: get;
15: private set;
16: }
17:
18: protected internal IEventAggregator EventAggregator
19: {
20: [DebuggerStepThrough]
21: get
22: {
23: return _eventAggregator;
24: }
25: }
26:
27: public void Start()
28: {
29: OnStart();
30: IsRunning = true;
31: }
32:
33: public void Stop()
34: {
35: OnStop();
36: IsRunning = false;
37: }
38:
39: protected abstract void OnStart();
40:
41: protected abstract void OnStop();
42:
43: protected internal SubscriptionToken Subscribe<TEvent, TEventArgs>(Action<TEventArgs> action) where TEvent : BaseEvent<TEventArgs> where TEventArgs : class
44: {
45: return _eventAggregator.GetEvent<TEvent>().Subscribe(action, true);
46: }
47:
48: protected internal void Unsubscribe<TEvent>(SubscriptionToken token) where TEvent : BaseEvent
49: {
50: _eventAggregator.GetEvent<TEvent>().Unsubscribe(token);
51: }
52: }
注意下 Subscribe和Unsubscribe方法,這兩個方法通過eventAggregator獲取特定的TEvent,實現事件的定訂閱和解除訂閱。
然後再來看一個具體的Task,比如PingServer:
PingServer繼承BaseBackgroundTask ,需要實現OnStart和OnStop,PingServer的作用是在釋出一篇story的時候通知ping伺服器,我更新了,你可以派你的爬蟲過來了……因此,在OnStart方法中,Subscribe了story提交事件--StorySubmitEvent,並指定用StorySubmitted方法來處理這個事件,因此StorySubmitted方法只需要實現傳送ping的程式碼就可以了。
1:
2: protected override void OnStart()
3: {
4: if (!IsRunning)
5: {
6: _storySubmitToken = Subscribe<StorySubmitEvent, StorySubmitEventArgs>(StorySubmitted);
7: _storyApproveToken = Subscribe<StoryApproveEvent, StoryApproveEventArgs>(StoryApproved);
8: }
9: }
10:
11: protected override void OnStop()
12: {
13: if (IsRunning)
14: {
15: Unsubscribe<StorySubmitEvent>(_storySubmitToken);
16: Unsubscribe<StoryApproveEvent>(_storyApproveToken);
17: }
18: }
19:
20: internal void StorySubmitted(StorySubmitEventArgs eventArgs)
21: {
22: SendPing();
23: }
24:
25: internal void StoryApproved(StoryApproveEventArgs eventArgs)
26: {
27: SendPing();
28: }
光有訂閱是不行的,同學們,還需要有釋出才行!關於釋出,看看StoryService就可以了,在這個service的Create函式中有這麼一段程式碼:
1: _eventAggregator.GetEvent<StorySubmitEvent>().Publish(new StorySubmitEventArgs(story,detailUrl));
OMG,這就是釋出嗎?