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集 ...
随机推荐
- hackerrank Diameter Minimization
瞬间移动 题意:构造一个所有点出度都为m的有向图最小化图的直径. 显然转成m进制来做就好了. #include<queue> #include<cstdio> #include ...
- 2017ICPC/广西邀请赛1001(水)HDU6181
A Math Problem Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)To ...
- Java入门篇(三)——Java流程控制
前两篇已经了解了Java语言基础,本篇开始Java的流程控制.流程控制对任何一门编程语言都是至关重要的,它提供了控制程序步骤的基本手段. 一.复合语句 Java语言的复合语句是以整个块区为单位的语句, ...
- Git服务搭建及github使用教程
.pos { position: fixed; top: 35%; left: 90% } .pos a { border: 2px solid white; background: #99CCFF; ...
- Spring框架学习笔记(9)——Spring对JDBC的支持
一.使用JdbcTemplate和JdbcDaoSupport 1.配置并连接数据库 ①创建项目并添加jar包,要比之前Spring项目多添加两个jar包c3p0-0.9.1.2.jar和mysql- ...
- slice、splice与split傻傻分不清
每每看到这几个,就蒙圈了,这都是啥呀? 既然这么容易混淆,我还是来做个小笔记吧,以便日后查阅: 1.slice(数组) 定义:slice() 方法可从已有的数组中返回选定的元素. 用法:array ...
- Vue.js 1.x 和 2.x 实例的生命周期
在Vue.js中,在实例化Vue之前,它们都是以HTML的文本形式存在文本编辑器中.当实例化后将经历创建.编译.销毁三个主要阶段. 以下是Vue.js 1.x 实例的生命周期图示: Vue.js 1 ...
- Linux 安装及配置 Nginx + ftp 服务器
Nginx 安装及配置 一.Nginx 简介: Nginx("engine x") 是一款是由俄罗斯的程序设计师 Igor Sysoev 所开发高性能的 Web和 反向代理服务器, ...
- dedecms中{dede:myad name='about'/} 修改内容
网站首页index.htm中调用这个命令,显示一段文字,但是想要修改内容.所以想知道这个命令指定的文件内容在哪里寻找或者指定内容在哪里修改? 匿名 | 浏览 6036 次 发布于2014-02-19 ...
- Web前端学习(1):上网的过程与网页的本质
"众里寻他千百度"--但是在信息化时代,我们只需要动动手指百度一下,google一下,便可以在网络上寻得我们想要查找的信息.我们或许都知道要如何在网上获得自己所需信息,但是上网的过 ...