首页 > 其他分享 >.Net Web项目中,实现轻量级本地事件总线 框架

.Net Web项目中,实现轻量级本地事件总线 框架

时间:2024-09-27 11:27:37浏览次数:1  
标签:Web 触发 总线 处理程序 事件 Net public 轻量级 消息

一、事件总线设计方案

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;
        });
    }
}

标签:Web,触发,总线,处理程序,事件,Net,public,轻量级,消息
From: https://www.cnblogs.com/kong-ming/p/18422632

相关文章

  • SAM-VMNet:用于冠状动脉造影血管分割的深度神经网络
    SAM-VMNet:用于冠状动脉造影血管分割的深度神经网络摘要冠状动脉疾病(CAD)是心血管领域最为普遍的一种疾病,也是全球范围内导致死亡的重要因素之一。冠状动脉造影(CTA)图像被视为冠心病诊断的权威标准,通过对CTA图像进行血管分割和狭窄检测,医生能够更精确地诊断冠心病。为了结合基......
  • Docker打包Net8.0镜像
    Docker常用命令Docker是一种用于构建、打包和运行应用程序的容器化工具,以下是一些常用的Docker命令及其说明:1.Docker基础命令dockerversion#查看Docker的版本信息dockerinfo#查看Docker系统信息dockerbuild-t<image_name>.#构建镜像dockerpullnginx......
  • 实战笔记:Vue2项目Webpack 3升级到Webpack 4的实操指南
    在Web开发领域,保持技术的更新是非常重要的。随着前端构建工具的快速发展,Webpack已经更新到5.x版本,如果你正在使用Vue2项目,并且还在使用Webpack3,那么是时候考虑升级一下Webpack了。我最近将我的Vue2项目从Webpack3升级到了Webpack4。以下是我升级过程中积累的经验和步骤,希望......
  • Web APIs 4:日期对象、时间戳、节点操作、swiper插件
    WebAPIs4(日期对象、节点操作、swiper插件)1.实例化日期对象获得当前时间:constdate=newDate()获得指定时间:constdata=newDate(‘2024-1-108:30:30’)2.日期对象方法方法作用说明getFullTear()获得年份获取四位年份getMonth()获得月份取值为0~11getDate()获取月......
  • 2025年最新退休年龄计算 | C# | .net core
    usingSystem;usingRetireUtility;namespaceConsoleApp1{internalclassProgram{publicstaticvoidMain(string[]args){//参照https://mp.weixin.qq.com/s/wqFAOhfNhfwMJKaQEE5lYQ进行测试varr1=Retire.C......
  • ifream跟webview 嵌套orchard core的登录
    orchardcore默认的安全策略很高。所以要设置后台要开启ocrs跟安全program.cs要配置cookie可以跨域。`builder.Services.ConfigureApplicationCookie(options=>{options.Cookie.SameSite=SameSiteMode.None;options.Cookie.SecurePolicy=......
  • Stargazers Ghost Network在GitHub平台上的隐性威胁
    CheckPointResearch近期曝光了一个名为StargazersGhostNetwork的分发即服务(DaaS)网络,该网络利用GitHub这一全球知名的代码托管平台,通过精心设计的“幽灵账户”来隐秘地传播恶意软件。这一发现再次凸显了IP地址查询在识别和阻断网络威胁中的重要作用。StargazersGhostNetwork的......
  • YOLOv9改进,YOLOv9主干网络替换为GhostNetV3(2024年华为提出的轻量化架构,全网首发),助力
    摘要GhostNetV3是由华为诺亚方舟实验室的团队发布的,于2024年4月发布。摘要:紧凑型神经网络专为边缘设备上的应用设计,具备更快的推理速度,但性能相对适中。然而,紧凑型模型的训练策略目前借鉴自传统模型,这忽略了它们在模型容量上的差异,可能阻碍紧凑型模型的性能提升。在本......
  • 欧姆龙PLC数据 转 profinet IO项目案例
    目录1 案例说明 12 VFBOX网关工作原理 13 准备工作 24 网关采集欧姆龙PLC数据 25 用PROFINETIO协议转发数据 56 案例总结 71 案例说明设置网关采集欧姆龙PLC数据把采集的数据转成profinetIO协议转发给其他系统。2 VFBOX网关工作原理VFBOX网关是协议转换网关,是把一种......
  • 欧姆龙PLC数据 转 profinet IO项目案例
    目录1 案例说明 12 VFBOX网关工作原理 13 准备工作 24 网关采集欧姆龙PLC数据 25 用PROFINETIO协议转发数据 56 案例总结 71 案例说明设置网关采集欧姆龙PLC数据把采集的数据转成profinetIO协议转发给其他系统。2 VFBOX网关工作原理VFBOX网关是协议转换网关,是把一......