浅入 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. 【AI 算法评测】BERT 对 NLP 效果的改善,不负众望!

    AI 在各大领域的发展有目共睹,而作为人工智能皇冠上的明珠--自然语言处理却成果了了,大多实现或者以半成品的形式躺在实验室中,或者仅仅作为某个产品的辅助功能.而这一情况在 BERT 出现后出现了很大的 ...

  2. 精讲响应式webclient第1篇-响应式非阻塞IO与基础用法

    笔者在之前已经写了一系列的关于RestTemplate的文章,如下: 精讲RestTemplate第1篇-在Spring或非Spring环境下如何使用 精讲RestTemplate第2篇-多种底层HT ...

  3. 修改linux 动态ip为静态ip

    vi /etc/sysconfig/network-scripts/ifcfg-ens33 BOOTPROTO=static 设置网卡引导协议为 静态 ONBOOT=yes 网卡开机自启动 配置[IP ...

  4. 介绍 golang json数据的处理

    原文链接:https://blog.csdn.net/weixin_43223076/article/details/83550229 demo1: package main import ( &qu ...

  5. go chan 缓存与阻塞

    原文链接:Go语言第十一课 并发(三)Channel缓存与阻塞 Channel的缓存 前面介绍过channel的创建方法: channel_test := make(chan string) 其实它完 ...

  6. windows操作报错:无法启动此程序,因为计算机中丢失api-ms-win-core-winrt-string-l1-1-0.dll

    在Windows上做提交svn操作时报错:无法启动此程序,因为计算机中丢失api-ms-win-core-winrt-string-l1-1-0.dll,如下图: 解决办法: 在 https://cn ...

  7. 分块练习C. interval

    分块练习C. interval 题目描述 \(N\)个数\(a_i\),\(m\)个操作 \(1\). 从第一个数开始,每隔\(k_i\)个的位置上的数增加\(x_i\) \(2\). 查询\(l\) ...

  8. 使用Spring Cloud Config统一管理配置,别再到处放配置文件了

    1 前言 欢迎访问南瓜慢说 www.pkslow.com获取更多精彩文章! 可配置是一个成熟软件系统应该提供的特性,而配置管理对于大型系统就显得十分重要,特别是对于拥有多个应用的微服务系统.可喜的是, ...

  9. 计算机网络-应用层(5)P2P应用

    P2P系统的索引:信息到节点位置(IP地址+端口号)的映射 在文件共享(如电驴中):利用索引动态跟踪节点所共享的文件的位置.节点需要告诉索引它拥有哪些文件.节点搜索索引从而获知能够得到哪些文件 在即时 ...

  10. Chrome开发者工具调试详解

    chrome开发者工具最常用的四个功能模块:元素(ELements).控制台(Console).源代码(Sources),网络(Network). 元素(Elements):用于查看或修改HTML元素 ...