在Biwen.QuickApi中整合一个极简的发布订阅(事件总线)

biwen,quickapi · 浏览次数 : 0

小编点评

## Biwen.QuickApi 中的事件总线示例 这篇代码展示了如何在 Biwen.QuickApi 中实现一个简单的事件总线。 **事件定义接口:** ```csharp public interface IEvent{} ``` **事件订阅者接口:** ```csharp public interface IEventSubscriber<T> where T : IEvent { Task HandleAsync(T @event, CancellationToken ct); int Order { get; } bool ThrowIfError { get; } } ``` **发布者:** ```csharp public class Publisher(IServiceProvider serviceProvider) { public async Task PublishAsync(T @event, CancellationToken cancellationToken) where T : IEvent { // 注册事件订阅者 var handlers = serviceProvider.GetServices>(); if (handlers is null) return; foreach (var handler in handlers.OrderBy(x => x.Order)) { try { await handler.HandleAsync(@event, ct); } catch (Exception e) { if (handler.ThrowIfError) throw; } } } } ``` **核心代码:** ```csharp public class MyEvent : BaseRequest & IEvent { [FromQuery] public string? Message { get; set; } } public class MyEventHandler : EventSubscriber { private readonly ILogger _logger; public MyEventHandler(ILogger logger) { _logger = logger; } public override async Task HandleAsync(MyEvent @event, CancellationToken ct) { _logger.LogInformation($"msg 2 : {event.Message}"); return Task.CompletedTask; } } ``` **测试代码:** ```csharp [QuickApi("event")] public class EventApi : BaseQuickApi { public override async ValueTask ExecuteAsync(MyEvent request) { // 发布事件 await PublishAsync(request); return IResultResponse.Content("send event"); } } ``` **运行结果:** 通过执行 `curl` 命令,我们可以发送一个事件并查看服务器日志,验证事件已成功发布并处理。 **注意:** * 代码中使用了 `ILogger` 进行日志记录。你可以根据实际需求选择不同的日志框架。 * 代码中使用了 `Order` 和 `ThrowIfError` 属性,您可以根据需要添加或移除这些属性。

正文

闲来无聊在我的Biwen.QuickApi中实现一下极简的事件总线,其实代码还是蛮简单的,对于初学者可能有些帮助 就贴出来,有什么不足的地方也欢迎板砖交流~

首先定义一个事件约定的空接口

    public interface IEvent{}

然后定义事件订阅者接口

public interface IEventSubscriber<T> where T : IEvent
    {
        Task HandleAsync(T @event, CancellationToken ct);
        /// <summary>
        /// 执行排序
        /// </summary>
        int Order { get; }

        /// <summary>
        /// 如果发生错误是否抛出异常,将阻塞后续Handler
        /// </summary>
        bool ThrowIfError { get; }
    }
    public abstract class EventSubscriber<T> : IEventSubscriber<T> where T : IEvent
    {
        public abstract Task HandleAsync(T @event, CancellationToken ct);
        public virtual int Order => 0;
        /// <summary>
        /// 默认不抛出异常
        /// </summary>
        public virtual bool ThrowIfError => false;
    }

接着就是发布者


internal class Publisher(IServiceProvider serviceProvider)
{
	public async Task PublishAsync<T>(T @event, CancellationToken ct) where T : IEvent
	{
		var handlers = serviceProvider.GetServices<IEventSubscriber<T>>();
		if (handlers is null) return;
		foreach (var handler in handlers.OrderBy(x => x.Order))
		{
			try
			{
				await handler.HandleAsync(@event, ct);
			}
			catch
			{
				if (handler.ThrowIfError)
				{
					throw;
				}
				//todo:
			}
		}
	}
}

到此发布订阅的基本代码也就写完了.接下来就是注册发布者和所有的订阅者了

核心代码如下:

        static readonly Type InterfaceEventSubscriber = typeof(IEventSubscriber<>);
        static readonly object _lock = new();//锁
        static bool IsToGenericInterface(this Type type, Type baseInterface)
        {
            if (type == null) return false;
            if (baseInterface == null) return false;
            return type.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == baseInterface);
        }
        static IEnumerable<Type> _eventHanlers = null!;
        static IEnumerable<Type> EventHandlers
        {
            get
            {
                lock (_lock)
                    return _eventHanlers ??= ASS.InAllRequiredAssemblies.Where(x =>
                    !x.IsAbstract && x.IsPublic && x.IsClass && x.IsToGenericInterface(InterfaceEventSubscriber));
            }
        }
		    //注册EventSubscribers
            foreach (var handlerType in EventHandlers)
            {
                var baseType = handlerType.GetInterfaces().First(x => x.IsGenericType && x.GetGenericTypeDefinition() == InterfaceEventSubscriber);
                services.AddScoped(baseType, handlerType);
            }
            //注册Publisher
            services.AddScoped<Publisher>();
		

至此发布订阅的代码也就完成了!
现在我们将发布订阅封装到QuickApi中使用:


internal interface IPublisher
{
	/// <summary>
	/// Event Publish
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="event">Event</param>
	/// <returns></returns>
	Task PublishAsync<T>(T @event, CancellationToken cancellationToken) where T : IEvent;
}

然后BaseQuickApi实现IPublisher接口


internal interface IQuickApi<Req, Rsp> : IHandlerBuilder, IQuickApiMiddlewareHandler, IAntiforgeryApi, IPublisher
{
    ValueTask<Rsp> ExecuteAsync(Req request);
}

// BaseQuickApi.PublishAsync
public virtual async Task PublishAsync<T>(T @event, CancellationToken cancellationToken = default) where T : IEvent
{
    using var scope = ServiceRegistration.ServiceProvider.CreateScope();
    var publisher = scope.ServiceProvider.GetRequiredService<Publisher>();
    await publisher.PublishAsync(@event, cancellationToken);
}

至此功能完成,接下来我们测试一下:


using Biwen.QuickApi.Events;
using Microsoft.AspNetCore.Mvc;

namespace Biwen.QuickApi.DemoWeb.Apis
{
    public class MyEvent : BaseRequest<MyEvent>,IEvent
    {
        [FromQuery]
        public string? Message { get; set; }
    }

    public class MyEventHandler : EventSubscriber<MyEvent>
    {
        private readonly ILogger<MyEventHandler> _logger;
        public MyEventHandler(ILogger<MyEventHandler> logger)
        {
            _logger = logger;
        }

        public override Task HandleAsync(MyEvent @event, CancellationToken ct)
        {
            _logger.LogInformation($"msg 2 : {@event.Message}");
            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// 更早执行的Handler
    /// </summary>
    public class MyEventHandler2 : EventSubscriber<MyEvent>
    {
        private readonly ILogger<MyEventHandler2> _logger;
        public MyEventHandler2(ILogger<MyEventHandler2> logger)
        {
            _logger = logger;
        }

        public override Task HandleAsync(MyEvent @event, CancellationToken ct)
        {
            _logger.LogInformation($"msg 1 : {@event.Message}");
            return Task.CompletedTask;
        }

        public override int Order => -1;

    }

    /// <summary>
    /// 抛出异常的Handler
    /// </summary>
    public class MyEventHandler3 : EventSubscriber<MyEvent>
    {
        private readonly ILogger<MyEventHandler3> _logger;
        public MyEventHandler3(ILogger<MyEventHandler3> logger)
        {
            _logger = logger;
        }

        public override Task HandleAsync(MyEvent @event, CancellationToken ct)
        {
            throw new Exception("error");
        }

        public override int Order => -2;

        public override bool ThrowIfError => false;

    }

    [QuickApi("event")]
    public class EventApi : BaseQuickApi<MyEvent>
    {
        public override async ValueTask<IResultResponse> ExecuteAsync(MyEvent request)
        {
            //publish
            await PublishAsync(request);
            return IResultResponse.Content("send event");
        }
    }
}

最后我们运行项目测试一下功能:

curl -X 'GET' \
  'http://localhost:5101/quick/event?Message=hello%20world' \
  -H 'accept: */*'

image

源代码我发布到了GitHub,欢迎star! https://github.com/vipwan/Biwen.QuickApi

与在Biwen.QuickApi中整合一个极简的发布订阅(事件总线)相似的内容:

在Biwen.QuickApi中整合一个极简的发布订阅(事件总线)

闲来无聊在我的Biwen.QuickApi中实现一下极简的事件总线,其实代码还是蛮简单的,对于初学者可能有些帮助 就贴出来,有什么不足的地方也欢迎板砖交流~ 首先定义一个事件约定的空接口 public interface IEvent{} 然后定义事件订阅者接口 public interface I

Biwen.Settings添加对IConfiguration&IOptions的集成支持

Biwen.Settings 是一个简易的配置项管理模块,主要的作用就是可以校验并持久化配置项,比如将自己的配置存储到数据库中,JSON文件中等 使用上也是很简单,只需要在服务中注入配置, 比如我们有一个GithubSetting的配置项,我们只需要定义好对象然后注入到Service中即可: [De

详解Web应用安全系列(8)不足的日志记录和监控

在Web安全领域,不足的日志记录和监控是一个重要的安全隐患,它可能导致攻击者能够更隐蔽地进行攻击,同时增加了攻击被检测和响应的难度。以下是对Web攻击中不足的日志记录和监控漏洞的详细介绍。 一、日志记录不足的问题 日志缺失或不完整 关键操作未记录:如用户登录、敏感数据访问、系统管理员操作等关键操作未

光伏储能电厂设备连接iec61850平台解决方案

在当今日益发展的电力系统中,光伏储能技术以其独特的优势逐渐崭露头角,成为可再生能源领域的重要组成部分。而在光伏储能系统的运行与监控中,通信协议的选择与实现则显得至关重要。本文将重点介绍光伏储能系统中的Modbus协议、电力IEC 61850平台,以及如何通过协议转换网关实现Modbus转IEC 61

Qt实现汽车仪表盘

在UI界面显示中,仪表盘的应用相对比较广泛,经常用于显示速度值,电压电流值等等,最终实现效果如下动态图片(文末提供给源工程下载): 主要包含以下绘制步骤: 绘制画布 /* * 绘制画布 */ void Widget::initCanvas(QPainter &painter) { //消除锯齿 pa

Java定时任务实现优惠码

在Java中实现定时任务来发放优惠码,我们可以使用多种方法,比如使用java.util.Timer类、ScheduledExecutorService接口,或者更高级的框架如Spring的@Scheduled注解。这里,我将以ScheduledExecutorService为例来展示如何实现这一功能

对Transformer的一些理解

在学习Transformer这个模型前对seq2seq架构有个了解时很有必要的 先上图 输入和输出 首先理解模型时第一眼应该理解输入和输出最开始我就非常纠结 有一个Inputs,一个Outputs(shift right)和一个Output Probabilities,首先需要借助这三个输入/输出来

详解Web应用安全系列(5)敏感数据泄露漏洞

在最近几年,这是最常见的,最具影响力的攻击。这个领域最常见的漏洞是不对敏感数据进行加密。在数据加密过程中,常见的问题是不安全的密钥生成和管理以及使用弱密码算法,弱协议和弱密码。特别是使用弱的哈希算法来保护密码。在服务端,检测数据传输过程中的数据弱点很容易,但检测存储数据的弱点却非常困难。 敏感数据泄

在C#中使用RabbitMQ做个简单的发送邮件小项目

在C#中使用RabbitMQ做个简单的发送邮件小项目 前言 好久没有做项目了,这次做一个发送邮件的小项目。发邮件是一个比较耗时的操作,之前在我的个人博客里面回复评论和友链申请是会通过发送邮件来通知对方的,不过当时只是简单的进行了异步操作。 那么这次来使用RabbitMQ去统一发送邮件,我的想法是通过

WPF在.NET9中的重大更新:Windows 11 主题

在2023年的2月20日,在WPF的讨论区,WPF团队对路线的优先级发起了一次讨论。 对三个事项发起了投票。 第一个是Windows 11 主题 第二个是更新的控件 第三个是可空性注释 最终Windows 11 主题得票最高,WPF团队2023-2024的工作优先级就是Windows 11 主题了。