在Biwen.QuickApi中整合一个极简的发布订阅(事件总线)
闲来无聊在我的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: */*'

源代码我发布到了GitHub,欢迎star! https://github.com/vipwan/Biwen.QuickApi
在Biwen.QuickApi中整合一个极简的发布订阅(事件总线)的更多相关文章
- Android 使用RxJava实现一个发布/订阅事件总线
1.简单介绍 1.1.发布/订阅事件主要用于网络请求的回调. 事件总线可以使Android各组件之间的通信变得简单,而且可以解耦. 其实RxJava实现事件总线和EventBus比较类似,他们都依据与 ...
- RELabel : 一个极简的正则表达式匹配和展示框架
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...
- Spring Boot(5)一个极简且完整的后台框架
https://blog.csdn.net/daleiwang/article/details/75007588 Spring Boot(5)一个极简且完整的后台框架 2017年07月12日 11:3 ...
- 借助腾讯云的云函数实现一个极简的API网关
借助腾讯云的云函数实现一个极简的API网关 Intro 微信小程序的域名需要备案,但是没有大陆的服务器,而且觉得备案有些繁琐,起初做的小程序都有点想要放弃了,后来了解到腾讯云的云函数,于是利用腾讯云的 ...
- Vue.js 入门:从零开始做一个极简 To-Do 应用
Vue.js 入门:从零开始做一个极简 To-Do 应用 写作时间:2019-12-10版本信息:Vue.js 2.6.10官网文档:https://cn.vuejs.org/ 前言 学习 Vue ...
- 【转】手摸手,带你用vue撸后台 系列四(vueAdmin 一个极简的后台基础模板)
前言 做这个 vueAdmin-template 的主要原因是: vue-element-admin 这个项目的初衷是一个vue的管理后台集成方案,把平时用到的一些组件或者经验分享给大家,同时它也在不 ...
- 我喜欢的两个js类实现方式 现在再加上一个 极简主义法
闭包实现 变量是不会变的:) var myApplication = function(){ var name = 'Yuri'; var age = '34'; var status = 'sing ...
- 使用apache-cxf-2.2.10来制作一个极简版WebService程序
原想拿最新版cxf来制作的,无奈Apache的zip包总下不下来,国内的apache-cxf-2.2.10却一蹴而就,也就用了这个版本.下载地址是:http://pan.baidu.com/s/1td ...
- 一个极简的守护进程Bash脚本
由于最近写的Node.js程序因为一些Bug,会出现一些自动退出的问题,所以需要在它退出的时候及时发现,并重新启动 于是查阅了些资料,写了一个Bash的程序,功能十分简单,就是每隔3s判断一次处在60 ...
- 飘逸的python - 实现一个极简的优先队列
一个队列至少满足2个方法,put和get. 借助最小堆来实现. 这里按"值越大优先级越高"的顺序. #coding=utf-8 from heapq import heappush ...
随机推荐
- 【已解决】ERROR 2003 (HY000): Can't connect to MySQL server on 'localhost' (10061)---mysql数据库本地服务器localhost连接失败
出现错误mysql数据库本地服务器localhost连接失败: 1.输入命令 mysql -uroot -p 输入密码进入数据库发现错误 2.输入命令 mysqld --install 出现Serv ...
- 动态数组(Array)
Array 存储具有一对一逻辑关系数据的存储顺序结构. 数组最大的优点:快速查询,最好应用于索引有语义的情况. 插入元素 template<typename T> bool Array&l ...
- JAVA下载文件防重复点击,防止多次下载请求,Cookie方式快速简单集成教程
JAVA下载文件防重复点击,防止多次下载请求,Cookie方式快速简单集成教程 JS文件在最下面: 引入 <script src="${path}/js/jquery-2.0.3.mi ...
- #树链剖分,线段树#洛谷 2146 [NOI2015]软件包管理器
题目传送门 分析 安装时1到\(x\)路径上都变为1,删除时\(x\)的子树都变为0, 显然可以用树链剖分+线段树实现 代码 #include <cstdio> #include < ...
- 批量拉取/git pull 指定文件夹下面所有 Git 项目的最新代码
背景 因为工作需要,当前所负责的项目较多:但是人力紧缺,其中绝大部分项目平时也不会去跟进迭代.所以经常需要批量拉取最新的代码查看最新的改动. 解决方案 一键批量拉取指定文件夹下所有 Git 项目的最新 ...
- R语言学习3:数据框处理(1)
本系列是一个新的系列,在此系列中,我将和大家共同学习R语言.由于我对R语言的了解也甚少,所以本系列更多以一个学习者的视角来完成. 参考教材:<R语言实战>第二版(Robert I.Kaba ...
- Device Partner平台合作伙伴认证和数据安全保护
Device Partner平台是面向AIoT产业链伙伴的一站式服务平台,伙伴可以通过平台获取最新的产品.服务与解决方案,实现智能硬件产品的开发.认证.量产和推广等全生命周期的管理,加入Harmon ...
- Error:A problem occurred configuring project ':app'.
前言 因为部门需要用到另外一个部门的库,而另外一个部门是c++部门,这种声音视频算法java做不了的. 如果是因为有引入c++库产生的这个问题,那么基本上你需要肯定你安装了ndk,如果没有安装那么会报 ...
- 【USENIX ATC】支持异构GPU集群的超大规模模型的高效的分布式训练框架Whale
简介: 高效大模型训练框架Whale(EPL)入选USENIX ATC 作者:张杰.贾贤艳 近日,阿里云机器学习PAI关于深度学习模型高效的分布式训练框架的论文< Whale: Efficien ...
- 时序数据库永远的难关 — 时间线膨胀(高基数 Cardinality)问题的解决方案
简介: 本文主要讨论 influxdb 在遇到写入的数据出现高基数 Cardinality 问题时,一些可行的解决方案. 作者 | 徐建伟 (竹影) 前序 随着移动端发展走向饱和,现在整个 IT 行 ...