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

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

时间:2024-09-29 10:45:53浏览次数:6  
标签:Web 触发 总线 处理程序 事件 Net public 轻量级 消息

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

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

相关文章

  • ‌Java JVM相当于.NET Core的CLR。
    ‌JavaJVM相当于.NETCore的CLR。Java虚拟机(JVM)和.NET的公共语言运行时(CLR)在内部工作方面有相似之处,但也有一些区别。JVM的主要作用是将编译后的Java字节码转换为特定计算机上的可执行代码,允许跨平台的执行,并提供内存管理和垃圾回收功能。CLR作为.NET框架的核心组件,也是将.NET代......
  • 第28篇 如何.net中实现高效可靠数据同步api
    通过以下方式可以高效,并保证数据同步的可靠性1.API设计使用RESTful设计,确保API端点明确,并使用适当的HTTP方法(如POST用于创建,PUT用于更新)。设计清晰的请求和响应模型,以确保客户端能够理解预期格式。2.数据验证在服务器端进行严格的数据验证,确保接收到的数据符合预期格式和......
  • javaweb基于SSH开发小型学生宿舍管理系统源码+报告 课程设计 大作业
    ......
  • VMware Tanzu Kubernetes Grid Integrated Edition 1.20 发布下载,新增功能概览
    VMwareTanzuKubernetesGridIntegratedEdition1.20发布下载,新增功能概览VMwareTanzuKubernetesGridIntegratedEdition(TKGI)1.20.0-运营商Kubernetes解决方案Kubernetes-basedcontainersolutionwithadvancednetworking,aprivatecontainerregistry,an......
  • idea启动卡在启动界面不动弹,java.net.BindException: Address already in use: bind
    早上刚想打开idea发现卡在启动界面无法动弹任务管理器关闭idea和重启机器都无法解决,搜了一下网上的教程把解决方法记录下:打开AppData\Local\JetBrains\IntelliJIdea2021.2\log查看idea.log发现详细错误如下:2024-09-2908:46:57,944[10149]ERROR-llij.ide.plugins.Plugi......
  • 在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
    前言:什么是操作筛选器操作筛选器是ASP.NETCoreWebAPI中的一种过滤器,用于在执行控制器操作(Action)之前或之后执行一些代码,完成特定的功能,比如执行日志记录、身份验证、授权、异常处理等通用的处理逻辑。每次ASP.NETCoreWebAPI中控制器的操作方法被执行的时候,操作筛选器......
  • 猜拳数据集-石头-剪刀-布数据集-YOLOv9 - YOLOv8 - YOLOv5 - YOLOv7 - COCO JSON - YO
    “石头-剪刀-布”计算机视觉项目是一个利用摄像头捕捉手势并识别出手势是石头、剪刀还是布的项目。这类项目通常用于学习和展示计算机视觉技术,如图像处理、特征提取以及机器学习或深度学习模型的应用。数据介绍rock-paper-scissorsComputerVisionProject数据集信息......
  • 【CTF Web】Pikachu 反射型xss(get) Writeup(反射型XSS+GET请求)
    XSS(跨站脚本)概述Cross-SiteScripting简称为“CSS”,为避免与前端叠成样式表的缩写"CSS"冲突,故又称XSS。一般XSS可以分为如下几种常见类型:1.反射性XSS;2.存储型XSS;3.DOM型XSS;XSS漏洞一直被评估为web漏洞中危害较大的漏洞,在OWASPTOP10的排名中一直属于前三的江湖地位......
  • 【CTF Web】BUUCTF SQLi-LABS Page-1(Basic Challenges) Less-12 Writeup(SQL注入+POST
    sqli-labs1点击启动靶机。SQLi-LABSPage-1(BasicChallenges)解法随便提交一些数据。审查元素。<formaction=""name="form1"method="post"> <divstyle="margin-top:15px;height:30px;">Username:&nbsp;&nbsp;&......
  • javaweb学习4
    今天主要学习了获取数据库连接的操作和mavenmaven导入mysql和druidjar包具体的jar坐标可以去这个网站找https://mvnrepository.com/<dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId......