浅入 ABP 系列(4):事件总线

版权护体作者:痴者工良,微信公众号转载文章需要 《NCC开源社区》同意。

这一篇将来学习 ABP 中的事件总线,然后结合在我们的基架项目中,逐渐构建一个完整的系统。

源码地址:https://github.com/whuanle/AbpBaseStruct

事件总线

关于事件总线

ABP 中,为了方便进程间通讯,给开发者提供了一个叫 事件总线 的功能,事件总线分为 本地事件总线分布式事件总线,本篇文章讲的是 本地事件总线,系列教程中暂时不考虑讲解 分布式事件总线

事件总线 需要使用 Volo.Abp.EventBus 库,ABP 包中自带,不需要额外引入。

事件总线是通过 订阅-发布 形式使用的,某一方只需要按照格式推送事件,而不需要关注是谁接收了事件和如何处理事件。

你可以参考官方文档:https://docs.abp.io/zh-Hans/abp/latest/Local-Event-Bus

为什么需要这个东西

首先列举一下,你工作开发的项目中,编写 控制器时,是不是有这几种代码。

// 记录日志 1
Task.Run(()=>
{
_apiLog.Info($"xxxxxxxx");
});
// 记录日志 2
catch(Exception ex)
{
_apiLog.Error(ex);
}
// 记录日志 3
_apiLog.Info($"登陆信息:用户 [{userName}({clientAdrress})]\);

笔者认为,改善的上述问的方法之一是将函数的功能跟记录日志分开,函数执行任务时,只需要把状态和信息通过事件总线推送,而不需要了关注应该如何处理这些内容。

另外,还有当函数执行某些步骤时,产生了事件,开发者喜欢 new Thread 一个新的线程去执行别的任务,或者 Task.Run

其实,通过事件总线,我们更加好地隔离代码,遵从 单一职责原则 。当然还有很多方面值得使用事件总线,这里我们就不再扯淡了。

前面,我们编写了全局异常拦截器,还有日志组件,这一篇我们将通过事件总线,将 Web 程序的一些部件组合起来。

事件总线创建过程

订阅事件

创建一个服务来订阅事件,当程序中发生某种事件时,此服务将被调用。

事件服务必须继承 ILocalEventHandler<in TEvent> 接口,并实现以下函数:

Task HandleEventAsync(TEvent eventData);

一个系统中,事件服务可以有多个,每个服务的 TEvent 类型不能相同,因为 TEvent 的类型是调用服务的标识。当发生 TEvent 事件后,系统通过 TEvent 去找到这个服务。

事件服务创建完毕后,需要加入到依赖注入中,你可以多继承一个 ITransientDependency 接口,然后统一扫描程序集加入到 依赖注入容器中(第三篇提到过)。

事件

即上面提到的 TEvent

假设有一个系统中所有的事件服务都放到一个容器中,发布者只能传递一个事件,而不能指定谁来提供响应服务。

容器是通过 TEvent 来查找服务的。

事件就是一个模型类,也可以使用 int或者 string 等简单类型(请不要用简单类型做事件),用于传递信息。

一般使用 Event 做后缀。

发布事件

如果需要发布一个事件,只需要注入 ILocalEventBus 即可。

        private readonly ILocalEventBus _localEventBus;

        public MyService(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
}

然后发布事件:

            await _localEventBus.PublishAsync(
new TEvent
{
... ...
}
);

全局异常加入事件总线功能

创建事件

AbpBase.Web 中,创建一个 Handlers 目录,再在 Handlers 目录下,创建 HandlerEvents 目录。

然后在 HandlerEvents 目录,创建一个 CustomerExceptionEvent.cs 文件。

CustomerExceptionEvent 作为一个异常事件,用于传递异常的信息,而不仅仅是将 Exception ex 记录就了事。

其文件内容如下:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text; namespace AbpBase.Application.Handlers.HandlerEvents
{
/// <summary>
/// 全局异常推送事件
/// </summary>
public class CustomerExceptionEvent
{
/// <summary>
/// 只记录异常
/// </summary>
/// <param name="ex"></param>
public CustomerExceptionEvent(Exception ex)
{
Exception = ex;
} /// <summary>
/// 此异常发生时,用户请求的路由地址
/// </summary>
/// <param name="ex"></param>
/// <param name="actionRoute"></param>
public CustomerExceptionEvent(Exception ex, string actionRoute)
{
Exception = ex;
Action = actionRoute;
} /// <summary>
/// 此异常发生在哪个类型的方法中
/// </summary>
/// <param name="ex"></param>
/// <param name="method"></param>
public CustomerExceptionEvent(Exception ex, MethodBase method)
{
Exception = ex;
MethodInfo = (MethodInfo)method;
} /// <summary>
/// 记录异常信息
/// </summary>
/// <param name="ex"></param>
/// <param name="actionRoute"></param>
/// <param name="method"></param>
public CustomerExceptionEvent(Exception ex, string actionRoute, MethodBase method)
{
Exception = ex;
Action = actionRoute;
MethodInfo = (MethodInfo)method;
} /// <summary>
/// 当前出现位置
/// <example>
/// <code>
/// MethodInfo = (MethodInfo)MethodBase.GetCurrentMethod();
/// </code>
/// </example>
/// </summary>
public MethodInfo MethodInfo { get; private set; } /// <summary>
/// 发生异常的 Action
/// </summary>
public string Action { get; private set; } /// <summary>
/// 具体异常
/// </summary>
public Exception Exception { get; private set; }
}
}

订阅事件

订阅事件,即将其定义为事件的响应者、服务提供者。

当异常发生后,异常的位置,推送异常信息,那么谁来处理这些信息呢?是订阅者。

这里我们定义一个异常日志处理类,来处理程序推送的异常信息。

AbpBase.Web 项目的 Handlers 目录中,添加一个 CustomerExceptionHandler 类,继承:

public class CustomerExceptionHandler : ILocalEventHandler<CustomerExceptionEvent>, ITransientDependency

服务要处理事件,必须继承 ILocalEventHandler<T>,而 ITransientDependency 是为了此服务可以可以自动注入到容器中。

其文件内容如下:

using AbpBase.Application.Handlers.HandlerEvents;
using Serilog;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus; namespace AbpBase.Application.Handlers
{
/// <summary>
/// 全局异常记录日志
/// </summary>
public class CustomerExceptionHandler : ILocalEventHandler<CustomerExceptionEvent>, ITransientDependency
{
private readonly ILogger _ILogger; public CustomerExceptionHandler(ILogger logger)
{
_ILogger = logger;
} public async Task HandleEventAsync(CustomerExceptionEvent eventData)
{
StringBuilder stringBuilder = new StringBuilder(256);
stringBuilder.AppendLine();
stringBuilder.Append("Action: ");
stringBuilder.AppendLine(eventData.Action);
if (eventData.MethodInfo != null)
{
stringBuilder.Append("Class-Method: ");
stringBuilder.Append(eventData.MethodInfo?.DeclaringType.FullName);
stringBuilder.AppendLine(eventData.MethodInfo?.Name);
} stringBuilder.Append("Source: ");
stringBuilder.AppendLine(eventData.Exception.Source);
stringBuilder.Append("TargetSite: ");
stringBuilder.AppendLine(eventData.Exception.TargetSite?.ToString());
stringBuilder.Append("InnerException: ");
stringBuilder.AppendLine(eventData.Exception.InnerException?.ToString());
stringBuilder.Append("Message: ");
stringBuilder.AppendLine(eventData.Exception.Message);
stringBuilder.Append("HelpLink: ");
stringBuilder.AppendLine(eventData.Exception.HelpLink);
_ILogger.Fatal(stringBuilder.ToString());
await Task.CompletedTask;
}
}
}

这样写,记录的日志可以有很好的层次结构。

发布事件

定义了事件的格式和定义服务来订阅事件后,我们来创建一个发布者。

我们修改一下 WebGlobalExceptionFilter

增加依赖注入:

        private readonly ILocalEventBus _localEventBus;

        public WebGlobalExceptionFilter(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
}

发布事件:

        public async Task OnExceptionAsync(ExceptionContext context)
{ if (!context.ExceptionHandled)
{
await _localEventBus.PublishAsync(new CustomerExceptionEvent(context.Exception,
context.ActionDescriptor?.DisplayName));
...
...

测试

创建一个 Action :

        [HttpGet("/T4")]
public string MyWebApi4()
{
int a = 1;
int b = 0;
int c = a / b;
return c.ToString();
}

然后访问 https://localhost:5001/T4 ,会发现请求后报错

AbpBase.WebLogs 目录中,打开 -Fatal.txt 文件。

可以看到:

2020-09-16 18:49:27.750 +08:00 [FTL]
Action: ApbBase.HttpApi.Controllers.TestController.MyWebApi4 (ApbBase.HttpApi)
Source: ApbBase.HttpApi
TargetSite: System.String MyWebApi4()
InnerException:
Message: Attempted to divide by zero.
HelpLink:

除了异常信息外,我们还可以很方便的知道异常发生在 TestController.MyWebApi4 这个位置。

记录事件

如果在普通方法里面出现异常,我们这样这样记录:

            catch (Exception ex)
{
...
new CustomerExceptionEvent(ex, MethodBase.GetCurrentMethod());
...
}

MethodBase.GetCurrentMethod() 可以获取当前正在运行的方法,获得信息后将此参数传递给异常记录服务,会自动解析出具体是哪个地方发生异常。

由于目前 Web 程序中还没有编写什么服务,因此我们先结合到异常日志功能中,后面编写服务时,会再次用到事件总线。

完整代码参考:https://github.com/whuanle/AbpBaseStruct/tree/master/src/4/AbpBase

下一篇文章地址是 https://www.cnblogs.com/whuanle/p/13061059.html

浅入 ABP 系列(4):事件总线的更多相关文章

  1. 浅入ABP(1):搭建基础结构的 ABP 解决方案

    浅入ABP(1):搭建基础结构的 ABP 解决方案 目录 浅入ABP(1):搭建基础结构的 ABP 解决方案 搭建项目基础结构 ApbBase.Domain.Shared 创建过程 ApbBase.D ...

  2. ABP理论学习之事件总线和领域事件

    返回总目录 本篇目录 事件总线 定义事件 触发事件 处理事件 句柄注册 取消注册 在C#中,我们可以在一个类中定义自己的事件,而其他的类可以注册该事件,当某些事情发生时,可以通知到该类.这对于桌面应用 ...

  3. ABP EventBus(事件总线)

    事件总线就是订阅/发布模式的一种实现    事件总线就是为了降低耦合 1.比如在winform中  到处都是事件 触发事件的对象  sender 事件的数据    e 事件的处理逻辑  方法体 通过E ...

  4. Android 开发 框架系列 EventBus 事件总线

    介绍 GitHub:https://github.com/greenrobot/EventBus 先聊聊EventBus 线程总线是干什么的,使用环境,优点.缺点. 干什么的? 一句话,简单统一数据传 ...

  5. VUE 入坑系列 一 事件

    html代码 <div id="app"> <button v-on:click="counter += 1">加1</butto ...

  6. ABP之事件总线(5)

    前面已经对Castle Windsor的基本使用进行了学习,有了这个基础,接下来我们将把我们的事件总线再次向ABP中定义的事件总线靠近.从源码中可以知道在ABP中定义了Dictionary,存放三种类 ...

  7. ABP官方文档翻译 3.7 领域事件(事件总线)

    领域事件(事件总线) 事件总线 注入IEventBus 获取默认实例 定义事件 预定义事件 处理异常 实体更改 触发事件 处理事件 处理基础事件 处理者异常 处理多个事件 注册处理者 自动 手动 取消 ...

  8. [Abp 源码分析]九、事件总线

    0.简介 事件总线就是订阅/发布模式的一种实现,本质上事件总线的存在是为了降低耦合而存在的. 从上图可以看到事件由发布者发布到事件总线处理器当中,然后经由事件总线处理器调用订阅者的处理方法,而发布者和 ...

  9. ABP之事件总线(4)

    在上一篇的随笔中,我们已经初步完成了EventBus,但是EventBus中还有诸多的问题存在,那么到底有什么问题呢,接下来我们需要看一看ABP中的源码是如何定义EventBus的. 1.第一个点 在 ...

随机推荐

  1. 【luogu1613】跑路 - 倍增+Floyd

    题目描述 小A的工作不仅繁琐,更有苛刻的规定,要求小A每天早上在6:00之前到达公司,否则这个月工资清零.可是小A偏偏又有赖床的坏毛病.于是为了保住自己的工资,小A买了一个十分牛B的空间跑路器,每秒钟 ...

  2. realm数据库报错:Changing Realm data can only be done from inside a transaction.

    在编写realm数据库相关时: 代码: List<Student> delByStudent(String priNum){ RealmResults<Student> stu ...

  3. (转)@Autowired(required=false)注入注意的问题

    1.前言 在使用spring开发过程中,我们基本上都是使用@Autowired这个注解,用来注入已有的bean.但是有些时候,会注入失败.当我们加上参数(required=false)就能解决.今天整 ...

  4. CodeQL CLI入门

    一.CodeQL CLI 安装和配置 1.下载CodeQL CLI 压缩包 https://github.com/github/codeql-cli-binaries/releases 2.创建Cod ...

  5. 用python实现实时监控网卡流量

    很多时候,我们是需要查看服务器的网卡当前跑了多大流量,但对于网卡流量的查询,在linux下似乎没有像top那样的原生命令.虽然top功能很强大,可以实时查看cpu.内存.进程的动态,但是却没有对网卡流 ...

  6. IA-32/centos7开机流程

    开机后系统首先在实地址模式下工作(只有1MB的寻址空间) 开机过程中,需要先准备在实模式下的中断向量表和中断服务程序.通常,由固化在主板上一块ROM芯片中的BIOS程序完成 加载BIOS的硬件信息,B ...

  7. ASP.NET Core3.1使用IdentityServer4中间件系列随笔(五):创建使用[Code-授权码]授权模式的客户端

    配套源码:https://gitee.com/jardeng/IdentitySolution 本篇将创建使用[Code-授权码]授权模式的客户端,来对受保护的API资源进行访问. 1.接上一篇项目, ...

  8. CSS 选择器及优先级

    CSS 选择器及优先级 1.根据权值计算 div .class1 #people的权值等于1+10+100=111 .class2 li #age的权值等于10+1+100=111 2.权值相同,那么 ...

  9. Qt 让Label显示图片并把图片居中

    Qt 让Label显示图片并把图片居中   QPixmap image("./13.jpg"); QPixmap fitpixmap=image.scaled(ui->lab ...

  10. 分享几个好用的ui框架,以便开发

    1:Layui--经典模块化前端框架 地址:https://www.layui.com/ 2:iview--基于 Vue.js 的高质量 UI 组件库 地址:http://v1.iviewui.com ...