ABP官方文档翻译 3.7 领域事件(事件总线)
领域事件(事件总线)
在C#中,一个类可以定义自己的事件,其他类可以注册它以便当一些事情发生时就会被通知。对于桌面应用或者单独的windows服务而言,这是非常有用的。但是,对于Web应用,会有一点儿问题,因为对象在web请求中被创建并且是短暂存在的。注册类事件非常困难。直接注册另一个类的事件会使类变得紧耦合。
在应用中,领域事件可以用来解耦业务逻辑且能够反应重要的领域更改。
事件总线
事件总线是一个单例对象,被其他所有的类共享来触发和处理事件。为了使用事件总线,你需要获取一个它的引用。可以使用两种方式来获取。
注入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自动记录所有的异常),这个会特别有用。你可以注册这个事件,当异常发生时便会被通知。
实体更改
实体变更也有通用的事件数据类。EntityCreatingEventData<TEntity>、EntityCreatedEventData<TEntity>、EntityUpdatingEventData<TEntity>、EntityUpdatedEventData<TEntity>、ENtityDeletingEventData<TEntity>和EntityDeletedEventData<TEntity>。还有EntityChangingEventData<TEntity>和EntityChangedEventData<TEntity>。更改可以插入、更新和删除。
‘ing’事件(例如:EntityUpdating)在保存更改之前触发。所以你可以通过在这些事件中抛出异常来回滚工作单元以阻止操作。‘ed’事件(例如:EntityUpdated)在保存更改之后发生,这时就没有机会回滚工作单元了。
实体更改事件在Abp.Events.Bus.Entities命名空间中定义,当一个实体被插入、更新或删除时,ABP自动触发。如果你有一个Person实体,可以注册到EntityCreatedEvnetData<Person>,当一个新Person被创建并插入到数据库时会接收到通知。这些事件支持继承。如果Student类继承自Person类且注册到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 = });
}
}
这有一些触发方法的重载版本:
EventBus.Trigger<TaskCompletedEventData>(new TaskCompletedEventData { TaskId = }); //Explicitly declare generic argument
EventBus.Trigger(this, new TaskCompletedEventData { TaskId = }); //Set 'event source' as 'this'
EventBus.Trigger(typeof(TaskCompletedEventData), this, new TaskCompletedEventData { TaskId = }); //Call non-generic version (first argument is the type of the event class)
另一种触发事件的方式是使用聚合根的领域事件集合。(在Entity documentation中查看相关部分)
处理事件
为了处理事件,你应该实现IEventHandler<T>接口,如下所示:
public class ActivityWriter : IEventHandler<TaskCompletedEventData>, ITransientDependency
{
public void HandleEvent(TaskCompletedEventData eventData)
{
WriteActivity("A task is completed by id = " + eventData.TaskId);
}
}
IEventHandler定义了HandleEvent方法,我们按如上所示实现它。
事件总线集成到了依赖注入系统。如我们上面实现的ITransientDependency,当一个TaskCompleted事件发生时,它创建一个ActivityWriter类的新实例,并调用它的HandleEvent方法,然后释放他。参见依赖注入了解更多。
处理基础事件
事件总线支持事件继承。例如,你创建了一个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>处理应用中的所有事件。你或许不希望这样,但是这是可以实现的。
处理者异常
事件总线触发所有的处理者,即使他们中的一个或一些抛出异常。如果他们中的一个抛出异常,然后他被触发方法直接抛出。如果多余一个处理者抛出异常,事件总线会抛出他们的一个聚合异常。
处理多个事件
你可以在一个处理者中处理多个事件。这时,你应该为每一个事件实现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中,推荐使用这种方式使用事件总线。
手动
手动注册也是可以的,但需要小心。在web应用中,事件注册需要在应用开始时完成。在一个web请求中注册一个事件不是一个好方式,因为被注册的类在请求结束后仍然保持被注册状态,且为每一次请求重新注册。这会导致你的应用出现问题,因为注册类会被调用多次。还要记得,手动注册不使用依赖注入系统。
这有一些事件总线注册方法的重载。最简单一个接收一个委托(或拉姆达表达式):
EventBus.Register<TaskCompletedEventData>(eventData =>
{
WriteActivity("A task is completed by id = " + eventData.TaskId);
});
因此当一个'task completed'事件发生时,会调用这个拉姆达表达式。第二个接收一个实现了IEventHandler<T>接口的对象:
EventBus.Register<TaskCompletedEventData>(new ActivityWriter());
多个事件会调用同一个ActivityWriter实例。这个方法有一个非泛型的重载版本。另一个重载接收两个泛型参数:
EventBus.Register<TaskCompletedEventData, ActivityWriter>();
这次,事件总线为每个事件创建一个ActivityWriter实例。如果它是一次性处理的,它将调用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 to an event...
var registration = EventBus.Register<TaskCompletedEventData>(eventData => WriteActivity("A task is completed by id = " + eventData.TaskId) ); //Unregister from event
registration.Dispose();
当然,取消注册可能在其他地方或其他时间。保留注册对象并当你想取消注册时释放它。注册方法的所有重载都返回一个可释放对象用来取消注册事件。
事件总线也提供了Unregister方法。示例用法:
//Create a handler
var handler = new ActivityWriter(); //Register to the event
EventBus.Register<TaskCompletedEventData>(handler); //Unregister from event
EventBus.Unregister<TaskCompletedEventData>(handler);
它也提供了重载来取消注册委托和工厂。取消注册处理者对象必须是之前注册的同一个对象。
最后,事件总线提供了一个UnregisterAll<T>方法用来取消注册一个事件的所有处理者和UnregisterAll()方法用来取消所有事件的所有注册者。
ABP官方文档翻译 3.7 领域事件(事件总线)的更多相关文章
- ABP官方文档翻译 3.4 领域服务
领域服务 介绍 IDomainService接口和DomainService类 示例 创建接口 服务实现 使用应用服务 一些探讨 为什么只有应用服务? 如何强制使用领域服务? 介绍 领域服务(或者在D ...
- ABP官方文档翻译 0.0 ABP官方文档翻译目录
一直想学习ABP,但囿于工作比较忙,没有合适的契机,当然最重要的还是自己懒.不知不觉从毕业到参加工作七年了,没留下点儿什么,总感觉很遗憾,所以今天终于卯足劲鼓起勇气开始写博客.有些事能做的很好,但要跟 ...
- ABP官方文档翻译 1.4 启动配置
启动配置 配置ABP 替换内置服务 配置模块 创建模块配置 ABP提供了基础设施和模型在启动的时候对它及模块进行配置. 配置ABP 在模块的PreInitialize事件中配置ABP.示例配置如下: ...
- ABP官方文档翻译 3.6 工作单元
工作单元 介绍 ABP中的连接和事务管理 传统的工作单元方法 控制工作单元 UnitOfWork特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 无事务工作单元 一个工作单元方法 ...
- ABP官方文档翻译 3.1 实体
实体 实体类 聚合根类 领域事件 常规接口 审计 软删除 激活/失活实体 实体改变事件 IEntity接口 实体是DDD(领域驱动设计)的核心概念之一.Eric Evans描述它为"An o ...
- ABP官方文档翻译 1.2 N层架构
N层架构 介绍 ABP架构 其他(通用) 领域层 应用层 基础设施层 网络和展现层 其他 总结 介绍 应用程序代码库的分层架构是被广泛认可的可以减少程序复杂度.提高代码复用率的技术.为了实现分层架构, ...
- ABP官方文档翻译 8.2 SignalR集成
SignalR集成 介绍 安装 服务器端 客户端 建立连接 內建特征 通知 在线客户端 PascalCase与CamelCase对比 你的SignalR代码 介绍 ABP中的Abp.Web.Signa ...
- ABP官方文档翻译 8.1 通知系统
通知系统 介绍 发送模型 通知类型 通知数据 通知严重性 关于通知持久化 订阅通知 发布通知 用户通知管理 实时通知 客户端 通知存储 通知定义 介绍 在系统中通知用来基于特定的事件告知用户.ABP提 ...
- ABP官方文档翻译 7.1 后台Jobs和Workers
后台Jobs和Workers 介绍 后台Jobs 关于Job持久化 创建后台Job 在队列中添加一个新Job 默认的后台Job管理器 后台Job存储 配置 禁用Job执行 异常处理 Hangfire集 ...
随机推荐
- CodeForces-2015 HIAST Collegiate Programming Contest-Gym-100952A-Who is the winner?
A. Who is the winner? time limit per test 1 second memory limit per test 64 megabytes input standard ...
- jsp的内置对象
JSP内置对象即无需声明就可以直接使用的对象实例,在实际的开发过程中,比较常用的JSP对象有request,response,session,out和application等,笔者在本文章中将简单介绍 ...
- Graph(Floyd)
http://acm.hdu.edu.cn/showproblem.php?pid=4034 Graph Time Limit: 2000/1000 MS (Java/Others) Memor ...
- js实现深拷贝和浅拷贝
浅拷贝: 思路----------把父对象的属性,全部拷贝给子对象,实现继承. 问题---------如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,不会开辟新栈,不是 ...
- HDU 2544 最短路(模板题——Floyd算法)
题目: 在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt.但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你 ...
- github中删除项目
- 谷歌扩展程序设置ajax请求允许跨域(极少人知道的解决方案)
前言: 跨域问题一直是个老生常谈的问题,在实际开发过程中,跨域的问题常常会让开发者非常的头疼. 常用的几种跨域解决方案: 1.代理 2.XHR2 HTML5中提供的XMLHTTPREQUEST Lev ...
- vue学习笔记(四)——Vue实例以及生命周期
1.Vue实例API 1.构造器(实例化) var vm = new Vue({ //选项 |-------DOM(3) | |-------el (提供一个在页面上已存在的 DOM 元素作为 V ...
- linux_DNS
linux其配置文件 : /etc/resolv.conf nameserver 223.5.5.5 nameserver 223.6.6.6 # 这两个解析地址为阿里云解析地址,格式也是这样 什么是 ...
- jsp页面从标签属性中获取值
你还可以在"data-*" 属性里使用json语法,例如 <div id="awesome-json" data-awesome='{"game ...