一、事件总线设计方案

1.1、事件总线的概念

  • 事件总线是一个事件管理器,负责统一处理系统中所有事件的发布和订阅。
  • 事件总线模式通过提供一种松耦合的方式来促进系统内部的业务模块之间的通信,从而增强系统的灵活性和可维护性。

1.2、实现的功能目标

  • 注入事件总线服务到DI容器,自动注入整个程序集的事件;
  • 每个事件处理程序能够自动依赖注入;
  • 通过特性标注事件消息模型、事件处理器;
  • 事件总线服务提供一个发布事件的方法,根据消息模型,自动找到并触发对应的事件处理程序,并传递事件参数。

二、使用案例

2.1、事件消息模型

  • 需要继承 EventArgs
public class UserTestEventArgs : EventArgs
{
public string UserId { get; set; }
public string UserName { get; set; }
}

2.2、事件处理程序

  • 该事件模型,触发的事件处理程序,会自动依赖注入
[LocalEventHandler(typeof(UserTestEventArgs), 1)]
public class UserTest1EventHandler(SingletonTestService singletonService, ScopeTestService scopeService, TransientTestService transientService) : ILocalEventHandler<UserTestEventArgs>
{
public Task OnEventHandlerAsync(object sender, UserTestEventArgs e)
{
Console.WriteLine($"事件1被'{sender.GetType().Name}'触发,参数:" + JsonUtils.ToJson(e));
try
{
singletonService.Test();
scopeService.Test();
transientService.Test();
}
catch (Exception ex)
{
}
return Task.CompletedTask;
}
}

2.3、注入事件总线服务到DI

  • 在Startup.cs 或 Program.cs 中,注入服务
builder.Services.AddLocalEventBus(typeof(UserTest1EventHandler).Assembly); // 注入事件总线服务,自动注册这个程序集内的所有事件处理器。

2.4、使用事件总线服务,触发事件

  • 通过构造函数依赖注入,拿到事件总线服务 ILocalEventBus
  • 调用事件总线服务,发布事件消息,触发事件处理程序 eventBus.PublishAsync(this, args);
// 事件总线测试控制器
public class EventTestController(
ILocalEventBus eventBus, // 主构造函数,依赖注入事件总线服务
IUserService userService // 测试服务
) : ControllerBase
{
[HttpPost]
public Task Test(UserTestEventArgs args) // UserTestEventArgs 事件消息模型
{
var users = userService.GetUsers();
return eventBus.PublishAsync(this, args); // 发布事件消息,触发事件处理程序。 this:触发事件的对象 args:事件消息
}
}

三、事件总线功能开发

3.1、本地事件总线 服务接口

  • 事件的发布方法设计,基于 .Net 标准事件模式 思想。
  • 这里需要泛型参数:事件消息模型类型,以便在触发事件时可以找到注册的该消息模型对应的事件处理器。
/// <summary>
/// 本地事件总线
/// </summary>
public interface ILocalEventBus
{
/// <summary>
/// 触发对应 事件消息模型 对应的 事件处理程序
/// </summary>
/// <typeparam name="TEventArgs">事件消息模型类型</typeparam>
/// <param name="sender">触发事件的对象</param>
/// <param name="args">事件消息模型</param>
Task PublishAsync<TEventArgs>(object sender, TEventArgs args) where TEventArgs : EventArgs;
}

3.2、事件处理器 泛型接口

/// <summary>
/// 本地事件 事件处理程序接口
/// </summary>
/// <typeparam name="TEventArgs">事件消息模型</typeparam>
public interface ILocalEventHandler<TEventArgs> where TEventArgs : EventArgs
{
/// <summary>
/// 事件处理程序方法
/// </summary>
/// <param name="sender">事件触发者</param>
/// <param name="e">事件消息</param>
Task OnEventHandlerAsync(object sender, TEventArgs e);
}

3.3、本地事件处理程序 特性

  • 本地事件处理程序 特性 :用于在事件处理器上标注。
  • 【MessageType】 指定该消息处理器,接受的消息模型类型。 在事件触发时,通过消息类型,找到该消息处理器,并调用。
  • 【Sort】如果多个消息处理器,声明接受同一个类型的消息模型。那么当这个类型的消息发布时,会触发这些多个事件处理程序,会通过指定的该触发顺序挨个触发。其中一个事件处理器执行报错,不会影响其他的。
/// <summary>
/// 本地事件处理程序 特性
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class LocalEventHandlerAttribute : Attribute
{
/// <summary>
/// 事件消息模型类,需要继承EventArgs
/// </summary>
public Type MessageType { get; set; }
/// <summary>
/// 触发顺序 正序
/// </summary>
public int Sort { get; set; } /// <summary>
/// 构造函数
/// </summary>
/// <param name="messageType">事件消息类型</param>
/// <param name="sort">触发顺序 正序</param>
public LocalEventHandlerAttribute(Type messageType, int sort = 0)
{
if(!messageType.IsSubclassOf(typeof(EventArgs)))
{
throw new Exception($"【LocalEventBus】The MessageType '{messageType.Name}' can not assignable from '{nameof(EventArgs)}'");
}
MessageType = messageType;
Sort = sort;
}
}

3.4、事件处理程序信息

/// <summary>
/// 事件处理程序模型
/// </summary>
public class LocalEventHandlerModel
{
/// <summary>
/// 触发顺序
/// </summary>
public int Sort { get; set; }
/// <summary>
/// 事件处理程序类型
/// </summary>
public Type HandlerType { get; set; }
}

3.5、事件总线服务

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Concurrent; namespace Singer.Framework.LocalEventBus; /// <summary>
/// 本地事件总线
/// </summary>
public sealed class LocalEventBus(IHttpContextAccessor httpContextAccessor) : ILocalEventBus
{
/// <summary>
/// 事件消息类型 - 对应的事件处理程序的类型集合
/// </summary>
private ConcurrentDictionary<Type, ConcurrentBag<LocalEventHandlerModel>> Events = new ConcurrentDictionary<Type, ConcurrentBag<LocalEventHandlerModel>>(); /// <summary>
/// 触发对应 事件消息模型 对应的 事件处理程序
/// </summary>
/// <typeparam name="TEventArgs">事件消息模型类型</typeparam>
/// <param name="sender">触发事件的对象</param>
/// <param name="args">事件消息模型</param>
public async Task PublishAsync<TEventArgs>(object sender, TEventArgs args)
where TEventArgs : EventArgs
{
var argType = typeof(TEventArgs);
if (!Events.TryGetValue(argType, out ConcurrentBag<LocalEventHandlerModel>? handlers))
return;
if (handlers == null || handlers.Count == 0)
return;
foreach (var handlerModel in handlers.OrderBy(x => x.Sort))
{
try
{
// 在此时 通过 DI 和事件处理程序类型 获取到 事件处理程序实例
var handlerInstance = httpContextAccessor?.HttpContext?.RequestServices?.GetService(handlerModel.HandlerType);
if (handlerInstance != null)
{
var method = handlerModel.HandlerType.GetMethod("OnEventHandlerAsync");
Task? task = (Task?)method?.Invoke(handlerInstance, [sender, args]);
if (task != null)
await task;
}
}
catch (Exception)
{
}
}
} /// <summary>
/// 向事件总线中添加多个事件处理程序
/// </summary>
/// <param name="handlerDic">事件消息类型 - 对应的事件处理程序模型列表 字典</param>
public void AddHandlers(Dictionary<Type, List<LocalEventHandlerModel>> handlerDic)
{
if (handlerDic == null || handlerDic.Count == 0)
return;
foreach (var item in handlerDic)
{
if (Events.TryGetValue(item.Key, out ConcurrentBag<LocalEventHandlerModel> handlerModels))
{
foreach (var value in item.Value)
{
handlerModels.Add(value);
}
}
else
{
Events.TryAdd(item.Key, new ConcurrentBag<LocalEventHandlerModel>(item.Value));
}
}
} }

3.6、事件总线服务 依赖注入处理

/// <summary>
/// 本地事件总线 服务拓展
/// </summary>
public static class LocalEventBusServiceExtensions
{
/// <summary>
/// 注册事件总线
/// 将整个程序集中带有EventHandler特性的事件处理程序类都注册到事件总线中
/// <param name="handlerAssembly">事件处理程序所在的程序集</param>
/// </summary>
public static void AddLocalEventBus(this IServiceCollection services, Assembly handlerAssembly)
{
var handlerTypes = handlerAssembly.GetTypes().Where(x => x.GetCustomAttribute<LocalEventHandlerAttribute>() != null);
Dictionary<Type, List<LocalEventHandlerModel>> handlerDic = new();
foreach (Type handlerType in handlerTypes)
{
services.Replace(new ServiceDescriptor(handlerType, handlerType, ServiceLifetime.Scoped)); // 将事件处理程序注入到容器中,这样事件处理程序也可以像普通服务一样使用依赖注入
var attribute = handlerType.GetCustomAttribute<LocalEventHandlerAttribute>();
if (attribute == null)
continue; var handlerModel = new LocalEventHandlerModel() { Sort = attribute.Sort, HandlerType = handlerType };
if (handlerDic.ContainsKey(attribute.MessageType))
{
handlerDic[attribute.MessageType].Add(handlerModel);
}
else
{
handlerDic.Add(attribute.MessageType, new List<LocalEventHandlerModel>() { handlerModel });
}
}
LocalEventBus? eventBus = services.BuildServiceProvider().GetService<ILocalEventBus>() as LocalEventBus; // 此方法可能多次调用,单例处理 services.AddHttpContextAccessor(); // 依赖Http上下文,通过HttpContext拿到每次事件触发时的 服务作用域
services.AddSingleton<ILocalEventBus, LocalEventBus>(sp => // 将事件总线 注册为 单例服务
{
IHttpContextAccessor httpContextAccessor = sp.GetRequiredService<IHttpContextAccessor>();
eventBus = eventBus ?? new LocalEventBus(httpContextAccessor);
eventBus.AddHandlers(handlerDic); // 将解析出来的事件处理程序添加到事件总线中
return eventBus;
});
}
}

.Net Web项目中,实现轻量级本地事件总线 框架的更多相关文章

  1. [Abp vNext 源码分析] - 13. 本地事件总线与分布式事件总线 (Rabbit MQ)

    一.简要介绍 ABP vNext 封装了两种事件总线结构,第一种是 ABP vNext 自己实现的本地事件总线,这种事件总线无法跨项目发布和订阅.第二种则是分布式事件总线,ABP vNext 自己封装 ...

  2. 转 web项目中的web.xml元素解析

    转 web项目中的web.xml元素解析 发表于1年前(2014-11-26 15:45)   阅读(497) | 评论(0) 16人收藏此文章, 我要收藏 赞0 上海源创会5月15日与你相约[玫瑰里 ...

  3. web项目中获取spring的bean对象

    Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架,如何在程序中不通过注解的形式(@Resource.@Autowired)获取Spring配置的bean呢? Bean工厂(c ...

  4. 在基于MVC的Web项目中使用Web API和直接连接两种方式混合式接入

    在我之前介绍的混合式开发框架中,其界面是基于Winform的实现方式,后台使用Web API.WCF服务以及直接连接数据库的几种方式混合式接入,在Web项目中我们也可以采用这种方式实现混合式的接入方式 ...

  5. (转)关于java和web项目中的相对路径问题

    原文:http://blog.csdn.net/yethyeth/article/details/1623283 关于java和web项目中的相对路径问题 分类: java 2007-05-23 22 ...

  6. linux 下用renameTo方法修改java web项目中文件夹名称问题

    经测试,在Linux环境中安装tomcat,然后启动其中的项目,在项目中使用java.io.File.renameTo(File dest)方法可行. 之前在本地运行代码可以修改,然后传到Linux服 ...

  7. 在java web项目中实现随项目启动的额外操作

    前言 在web项目中经常会遇到在项目启动初始,会要求做一些逻辑的实现,比如实现一个消息推送服务,实现不同类型数据同步的回调操作初始化,或则通知其他客户服务器本项目即将启动,等等.对于这种要求,目前个人 ...

  8. asp.net Web项目中使用Log4Net进行错误日志记录

      使用log4net可以很方便地为应用添加日志功能.应用Log4net,开发者可以很精确地控制日志信息的输出,减少了多余信息,提高了日志记录性能.同时,通过外部配置文件,用户可以不用重新编译程序就能 ...

  9. Java Web项目中使用Freemarker生成Word文档

    Web项目中生成Word文档的操作屡见不鲜.基于Java的解决方式也是非常多的,包含使用Jacob.Apache POI.Java2Word.iText等各种方式,事实上在从Office 2003開始 ...

  10. web项目中nicedit富文本编辑器的使用

    web项目中nicedit富文本编辑器的使用 一.为什么要用富文本编辑器? 先说什么是富文本编辑器吧,普通的html中input或textarea标签只能进行简单的输入,而做不到其他的文本调整功能,甚 ...

随机推荐

  1. 靶机: EvilBox---One

    靶机: EvilBox---One 准备工作 靶机地址: https://download.vulnhub.com/evilbox/EvilBox---One.ova MD5 校验:c3a65197b ...

  2. Pandas库学习笔记(6) -- Pandas 基本方法

    Pandas 基本方法实例 到目前为止,我们了解了三个Pandas DataStructures以及如何创建它们.由于它在实时数据处理中的重要性,因此我们将主要关注DataFrame对象,并讨论其他一 ...

  3. Git 克隆仓库报unable to get local issuer certificate错误解决方法

    Git 克隆仓库报unable to get local issuer certificate错误解决方法 By:授客 QQ:1033553122 问题描述 克隆gitlab上的仓库,报错,如下 $ ...

  4. 入门到精通rsync和inotify

    rsync 作用: 实现文件的备份 备份位置可以是当前主机,也可以是远程主机 备份过程可以是完全备份,也可以是增量备份 功能: 1)类似于cp的复制功能 将本地主机的一个文件复制到另一个位置下 2)将 ...

  5. 【ActiveJdbc】03

    一.查询API 简单条件筛选: List<Person> list = Person.where("name = 'John'") 动态参数条件: List<Pe ...

  6. 【CentOS】tar包安装Tomcat

    下载Linux版本的Tomcat[Tar包] 上传到Linux 解压Tar包 tar -zxvf apache-tomcat-8.5.55.tar.gz 目录重命名简化名称[可不做] mv apach ...

  7. 【ECharts】02 饼图

    饼状图: <!-- 为ECharts准备一个具备大小(宽高)的Dom --> <div id="main" style="width: 600px;he ...

  8. 公开号CN117354339A —— 数据传输专利 —— 解决了相关技术在进行数据传输的过程中时效性较差的技术问题

    看到一个新闻: 地址: https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_92907119 ...

  9. Gitee官网大规模封禁开源项目,如想解禁则需手动提交审核,在此过程中一些项目的信息也被gitee官方修改!!!

    由于美国政府对中国的各种打压和制裁,为了支持国产软件我已经将GitHub上的大多数代码库迁移到了gitee上,虽然我的开源库基本都是个人学习时候的一些代码并不是什么成品项目代码,但是不管力量大小也都支 ...

  10. 链接池偶尔报错:HikariPool-1 - Connection is not available, request timed out after 39985ms.

    1.背景 线上服务器偶尔报错如下: org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.e ...