首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

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

编程知识
2024年09月27日 10:21

一、事件总线设计方案

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

From:https://www.cnblogs.com/kong-ming/p/18422632
本文地址: http://www.shuzixingkong.net/article/2348
0评论
提交 加载更多评论
其他文章 Java如何解决同时出库入库订单号自动获取问题
本文简要介绍了在Java中处理同时出库和入库的订单号自动获取问题,通常涉及到多线程环境下的并发控制。为了确保订单号的唯一性和连续性,我们可以使用多种策略,如数据库的自增ID、分布式锁、或者利用Java的并发工具类如`AtomicLong`等。
深入理解 Nuxt.js 中的 app:error 钩子
title: 深入理解 Nuxt.js 中的 app:error 钩子 date: 2024/9/27 updated: 2024/9/27 author: cmdragon excerpt: 摘要:本文深入讲解了Nuxt.js框架中的app:error钩子,介绍其在处理web应用中致命错误的重要作
深入理解 Nuxt.js 中的 app:error 钩子 深入理解 Nuxt.js 中的 app:error 钩子
在 ArkTS 中,如何有效地进行内存管理和避免内存泄漏?
ArkTS 是鸿蒙生态的应用开发语言,它在 TypeScript 的基础上进行了优化和定制,以适应鸿蒙系统的需求。 以下是在 ArkTS 中进行有效的内存管理和避免内存泄漏: 1. 使用 const 和 let 合理声明变量: 使用 const 声明那些不会重新赋值的变量,这有助于确保变量的不变性,
从零开始学机器学习——准备和可视化数据
首先给大家介绍一个很好用的学习地址:https://cloudstudio.net/columns 数据准备-清洗 在进行机器学习的第一步——准备数据,为了方便起见,我已经提前下载好了所需的文件。 https://files.cnblogs.com/files/guoxiaoyu/US-pumpki
从零开始学机器学习——准备和可视化数据 从零开始学机器学习——准备和可视化数据 从零开始学机器学习——准备和可视化数据
.NET 开源高性能 MQTT 类库
前言 随着物联网(IoT)技术的迅猛发展,MQTT(消息队列遥测传输)协议凭借其轻量级和高效性,已成为众多物联网应用的首选通信标准。 MQTTnet 作为一个高性能的 .NET 开源库,为 .NET 平台上的 MQTT 客户端与服务器开发提供了强大的支持。 本文将全面介绍 MQTTnet 的核心功能
.NET 开源高性能 MQTT 类库
manim边学边做--图形间集合关系
几何图形间的集合关系,是数学和几何学中的一个基本概念, 通过计算不同形状(如圆形、矩形、三角形等)的交集和并集等关系,可以实现复杂的图形处理和视觉效果。 manim中提供了4种计算几何形状间集合关系的模块: Difference:从形状A中减去与形状B相交的部分 Exclusion:减去形状A和形状
manim边学边做--图形间集合关系 manim边学边做--图形间集合关系 manim边学边做--图形间集合关系
通过MobaXterm操作远程服务器电脑的方法
本文介绍在Windows电脑中,下载、配置MobaXterm软件,从而连接、操作远程服务器的方法。 因为是在Windows操作系统的电脑中连接服务器,所以建议使用MobaXterm、PuTTY等可视化的远程计算机管理软件,来实现对服务器的连接与后续相关操作。在本文中,我们就选择基于MobaXterm
通过MobaXterm操作远程服务器电脑的方法 通过MobaXterm操作远程服务器电脑的方法 通过MobaXterm操作远程服务器电脑的方法
浅谈数栈产品里的 Descriptions 组件
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。 本文作者:修能 What&#39;s? 数栈产品里的 Descriptions 组件实际上就是 antd 的 Descriptions 组件,那么 antd 的 Des
浅谈数栈产品里的 Descriptions 组件 浅谈数栈产品里的 Descriptions 组件 浅谈数栈产品里的 Descriptions 组件