.Net Web项目中,实现轻量级本地事件总线 框架
一、事件总线设计方案
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项目中,实现轻量级本地事件总线 框架的更多相关文章
- [Abp vNext 源码分析] - 13. 本地事件总线与分布式事件总线 (Rabbit MQ)
一.简要介绍 ABP vNext 封装了两种事件总线结构,第一种是 ABP vNext 自己实现的本地事件总线,这种事件总线无法跨项目发布和订阅.第二种则是分布式事件总线,ABP vNext 自己封装 ...
- 转 web项目中的web.xml元素解析
转 web项目中的web.xml元素解析 发表于1年前(2014-11-26 15:45) 阅读(497) | 评论(0) 16人收藏此文章, 我要收藏 赞0 上海源创会5月15日与你相约[玫瑰里 ...
- web项目中获取spring的bean对象
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架,如何在程序中不通过注解的形式(@Resource.@Autowired)获取Spring配置的bean呢? Bean工厂(c ...
- 在基于MVC的Web项目中使用Web API和直接连接两种方式混合式接入
在我之前介绍的混合式开发框架中,其界面是基于Winform的实现方式,后台使用Web API.WCF服务以及直接连接数据库的几种方式混合式接入,在Web项目中我们也可以采用这种方式实现混合式的接入方式 ...
- (转)关于java和web项目中的相对路径问题
原文:http://blog.csdn.net/yethyeth/article/details/1623283 关于java和web项目中的相对路径问题 分类: java 2007-05-23 22 ...
- linux 下用renameTo方法修改java web项目中文件夹名称问题
经测试,在Linux环境中安装tomcat,然后启动其中的项目,在项目中使用java.io.File.renameTo(File dest)方法可行. 之前在本地运行代码可以修改,然后传到Linux服 ...
- 在java web项目中实现随项目启动的额外操作
前言 在web项目中经常会遇到在项目启动初始,会要求做一些逻辑的实现,比如实现一个消息推送服务,实现不同类型数据同步的回调操作初始化,或则通知其他客户服务器本项目即将启动,等等.对于这种要求,目前个人 ...
- asp.net Web项目中使用Log4Net进行错误日志记录
使用log4net可以很方便地为应用添加日志功能.应用Log4net,开发者可以很精确地控制日志信息的输出,减少了多余信息,提高了日志记录性能.同时,通过外部配置文件,用户可以不用重新编译程序就能 ...
- Java Web项目中使用Freemarker生成Word文档
Web项目中生成Word文档的操作屡见不鲜.基于Java的解决方式也是非常多的,包含使用Jacob.Apache POI.Java2Word.iText等各种方式,事实上在从Office 2003開始 ...
- web项目中nicedit富文本编辑器的使用
web项目中nicedit富文本编辑器的使用 一.为什么要用富文本编辑器? 先说什么是富文本编辑器吧,普通的html中input或textarea标签只能进行简单的输入,而做不到其他的文本调整功能,甚 ...
随机推荐
- ComfyUI进阶:Comfyroll插件 (六)
ComfyUI进阶:Comfyroll插件 (六) 前言: 学习ComfyUI是一场持久战,而Comfyroll 是一款功能强大的自定义节点集合,专为 ComfyUI 用户打造,旨在提供更加丰富和专业 ...
- 题解 CF741E Arpa’s abnormal DNA and Mehrdad’s deep interest
CF741E Arpa's abnormal DNA and Mehrdad's deep interest 记 \(R_{i}\) 表示把 \(T\) 插入在 \(S\) 的第 \(i\) 位后组成 ...
- [SDR] GNU Radio 系列教程 —— GNU Radio TX PDU (发送数据包操作)的基础知识(超全)
目录 1 PDU 概述 2 Demo 详解 2.1 Random PDU Generator 2.2 Async CRC32 2.3 Protocol Formatter (Async) 2.4 将 ...
- 安卓开发(java.lang.NullPointerException: Attempt to invoke virtual method ‘void android.view.View...)空指针异常
无论是初学者还是做开发很久的人都会遇到这个问题,那就是空指针异常: 遇到这种情况我们首先不要惊慌,一般这个问题都不是很大的问题,只需要我们 静下心来慢慢的查找,下面分成几步来带你查找问题: 1:首先是 ...
- 【SpringMVC】10 对Ajax的应用
编写一个AjaxController package cn.dai.controller; import org.springframework.web.bind.annotation.GetMapp ...
- 【Vue】Re09 Webpack 第一部分(介绍、安装、配置)
一.Webpack的用途 webpack要解决的是统一网页资源的问题 前端工程化出现了很多问题,就是兼容性,浏览器所不能解析 所以需要一个打包,转换等方式处理 二.安装描述介绍 下载安装NodeJS, ...
- 在计算机论文中suppose suggest assume 用法上的区别
ChatGPT3.5的答案: 在计算机论文中,"suppose," "suggest," 和 "assume" 有不同的用法和含义.它们在表 ...
- MAML —— Model-Agnostic Meta-Learning for Fast Adaptation of Deep Networks
论文地址: https://arxiv.org/abs/1703.03400 官方代码: 有监督学习: https://github.com/cbfinn/maml 强化学习: https://git ...
- tensorflow1.x——如何在python多线程中调用同一个session会话
如何在python多线程中调用同一个session会话? 这个问题源于我在看的一个强化学习代码: https://gitee.com/devilmaycry812839668/scalable_age ...
- Linux与windows共享文件的神器:samba
一.什么是samba? 搭建Samba服务器是为了实现Linux共享目录之后,在Windows可以直接访问该共享目录. 现在介绍如何在ubuntu 16.04系统中搭建Samba服务. 二 .samb ...