文檔目錄
本節內容:
在C#裡,一個類可以定義自己的事件,然後其它類可以注冊它,當某些事情發生時,接收到通知。這對於桌面應用或單機的Windows服務非常有用。但是,對於一個Web應用,它就有點問題,因為對象在一個web請求裡創建,並且它們生命周期都很短。所以就難於注冊一些類事件,同時,直接注冊另一個類的事件,也使得類之間更加藕合。
領域事件一般用來解藕業務邏輯和在應用裡發生重要領域修改時發出通知。
EventBus
EventBus是一個單例對象,被所有類觸發事件或處理事件時共享。為使用事件總線,你先要引用它,有兩種方式。
注入 IEventBus
你可以用依賴注入獲取一個IEventBus的引用,這兒我們使用屬性注入模式:
public class TaskAppService : ApplicationService { public IEventBus EventBus { get; set; } public TaskAppService() { EventBus = NullEventBus.Instance; } }
在注入事件總線上,屬性注入比構造器注入更合適。你的類可以沒有事件總線,NullEventBus實現了空對象模式,當你調用它的方法時,方法裡什麼也不做。
獲取默認實例
如果你不能注入它,可以直接使用EventBus.Default。它是全局的事件總線,使用方式如下所示:
EventBus.Default.Trigger(...); //trigger an event
在任何可能的地方都不建議直接使用EventBus.Default,因為它難於單元測試。
定義事件
在觸發一個事件前,你首先要定義它,通過一個繼承自EventData的類來表現一個事件。假設當一個任務完成後我們想觸發一個事件:
public class TaskCompletedEventData : EventData { public int TaskId { get; set; } }
這個類包含處理事件類所需要的屬性,EventData類定義了EventSource(事件源,哪個對象觸發了事件)和EventTime(何時觸發的)屬性。
預定義事件
處理完異常
ABP定義了AbpHandledExceptionData,並當ABP自動處理任何異常時,會觸發這個事件,這在你想了解更多異常信息時尤其有用(盡管ABP自動記錄了所有異常)。你可以注冊這個事件,當異常發生時,發出通知。
實體修改
為實體修改提供了泛型的事件:EntityCreationEventData<Tentity>、EntityCreatedEventData<TEntity>、EntityUpdatingEventData<TEntity>、EntityUpdateEventData<TEntity>、EntityDeletingEventData<TEntity>和EntityDeletedEventData<TEntity>,同樣也有EntityChangingEventData<TEntity>和EntityChangedEventData<TEntity>,修改可以是插入、更新或刪除。
“ing”事件(例如EntityUpdating)在保存修改(SaveChanges)前觸發,所以你可以在這些事件裡,通過拋出異常,促使工作單元回滾,阻止操作)。“ed”事件(例如EntityUpdated)在保存修改之後被觸發,也就沒有機會讓工作單元回滾了。
實體修改事件定義在Abp.Events.Bus.Entities命名空間裡,並在插入、更新或刪除實體時,被ABP自動觸發。如果你有一個Person實體,你可以注冊EntityCreatedEventData<Person>,當一個新的Person創建並插入到數據庫後,就可以收到通知。這些事件也支持繼承,如果你有一個繼承自Person的Student類,並且注冊了EntityCreatedEventData<Person>,當一個Person或Student被插入後,你也會收到通知。
觸發事件
觸發一個事件很簡單:
public class TaskAppService : ApplicationService { public IEventBus EventBus { get; set; } public TaskAppService() { EventBus = NullEventBus.Instance; } public void CompleteTask(CompleteTaskInput input) { //TODO: complete the task on database... EventBus.Trigger(new TaskCompletedEventData {TaskId = 42}); } }
Trigger方法有幾個重載:
EventBus.Trigger<TaskCompletedEventData>(new TaskCompletedEventData { TaskId = 42 }); //Explicitly declare generic argument EventBus.Trigger(this, new TaskCompletedEventData { TaskId = 42 }); //Set 'event source' as 'this' EventBus.Trigger(typeof(TaskCompletedEventData), this, new TaskCompletedEventData { TaskId = 42 }); //Call non-generic version (first argument is the type of the event class)
觸發事件的另一個方法是:使用AggregateRoot類的DomainEvents集合(查看實體文檔的相關小節)。
處理事件
為處理一個事件,你應該實現IEventHandler<T>接口,如下所示:
public class ActivityWriter : IEventHandler<TaskCompletedEventData>, ITransientDependency { public void HandleEvent(TaskCompletedEventData eventData) { WriteActivity("A task is completed by id = " + eventData.TaskId); } }
IEventHandler定義了HandleEvent方法,並像上面那樣實現它。
EventBus被整合到依賴注入系統裡,如我們上面那樣實現ITransientDependency,當一個TaskCompleted事件發生後,它創建一個新的ActivityWriter實例,並調用它的HandleEvent方法,然後釋放它,更多信息查看依賴注入。
處理基類事件
EventBus支持事件的繼承,例如:你可以創建一個TaskEventData和兩個子類:TaskCompletedEventData和TaskCreatedEventData:
public class TaskEventData : EventData { public Task Task { get; set; } } public class TaskCreatedEventData : TaskEventData { public User CreatorUser { get; set; } } public class TaskCompletedEventData : TaskEventData { public User CompletorUser { get; set; } }
然後你可以實現IEventhandler<TaskEventData>來處理這兩種事件:
public class ActivityWriter : IEventHandler<TaskEventData>, ITransientDependency { public void HandleEvent(TaskEventData eventData) { if (eventData is TaskCreatedEventData) { //... } else if (eventData is TaskCompletedEventData) { //... } } }
這也就意味著,你可以實現IEventHandler<EventData>來處理應用中的所有事件,你可能不想這樣做,但它是可以做到的。
處理程序異常
在處理程序(Handler)拋出一個/一些異常時,Eventbus觸發所有Handler事件,如果只有一個處理程序拋出異常,異常會直接被Trigger方法拋出,如果多個處理程序拋出異常,EventBus只為它們拋出一個AggregateException異常。
處理多個事件
在一個處理程序裡你可以處理多個事件,此次,你應該為每個事件實現IEventHandler<T>,例如:
public class ActivityWriter : IEventHandler<TaskCompletedEventData>, IEventHandler<TaskCreatedEventData>, ITransientDependency { public void HandleEvent(TaskCompletedEventData eventData) { //TODO: handle the event... } public void HandleEvent(TaskCreatedEventData eventData) { //TODO: handle the event... } }
處理程序注冊
為處理事件,我們必須在事件總線裡注冊處理程序。
自動
ABP找到所有實現IEVentHandler的類並注冊到依賴注入(例如:通過實現ITransientDependency,如上面的示例),然後ABP自動把它們注冊到事件總線,當一個事件發生,ABP使用依賴注入得到處理程序的引用,並在事件處理後釋放該引用。在ABP裡,這是使用事件總線的推薦的方式。
手動
可以手動注冊事件,但要小心使用。在一個web應用裡,事件注冊應當中應用啟動裡完成。在一個Web請求裡,注冊事件不是一個好的方式,因為注冊類請完成後繼續注冊,並為每個請求重新注冊,這可能會引起問題,因為注冊類多次被調用。同時要記住,手動注冊不使用依賴注入系統。
事件總線的Register方法有幾個重載,最簡單的是接受一個委托(或lambda):
EventBus.Register<TaskCompletedEventData>(eventData => { WriteActivity("A task is completed by id = " + eventData.TaskId); });
“任務完成”事件發生後,這個lambda方法就會被調用。第二個是接受一個實現了IEventHantler<T>的對象:
EventBus.Register<TaskCompletedEventData>(new ActivityWriter());
同樣是為事件調用ActivityWriter實例。第三個重載接受兩個泛型參數:
EventBus.Register<TaskCompletedEventData, ActivityWriter>();
此次,事件總線為每個事件創建一個新的ActivityWriter,如果它是disposable(可釋放),並調用ActivityWriter.Dispose方法。
最後,你可以注冊一個事件處理程序工作,負責處理程序的創建。一個處理程序工廠有兩個方法:GetHandler和ReleaseHandler。例如:
public class ActivityWriterFactory : IEventHandlerFactory { public IEventHandler GetHandler() { return new ActivityWriter(); } public void ReleaseHandler(IEventHandler handler) { //TODO: release/dispose the activity writer instance (handler) } }
還有一個特殊的工廠類IocHandlerFactory。它使用依賴注入系統來創建/釋放處理程序。ABP在自動注冊裡也使用這個類,所以,如果你想使用依賴注入系統,直接使用之前定義的自動注冊。
反注冊
當你向事件總線注冊後,想反注冊事件,最簡單的方式就是釋放Register方法返回的值,例如:
//Register to an event... var registration = EventBus.Register<TaskCompletedEventData>(eventData => WriteActivity("A task is completed by id = " + eventData.TaskId) ); //Unregister from event registration.Dispose();
當然,其它地方或其它某個時刻,都可能需要反注冊,你可以保存注冊對象並在需要時釋放它。Register方法的所有重載都返回一個可釋放的對象給事件。
EventBus也提供了Unregister方法,使用示例:
//Create a handler var handler = new ActivityWriter(); //Register to the event EventBus.Register<TaskCompletedEventData>(handler); //Unregister from event EventBus.Unregister<TaskCompletedEventData>(handler);
它也提供了重載來反注冊委托和工廠。反注冊處理程序對象必須是注冊時的對象。
最後,EventBus提供了一個UnregisterAll<T>()方法,它反注冊一個事件的所有處理程序,UnregisterAll()方法反注冊所有事件的所有處理程序。