闲来无聊在我的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中整合一个极简的发布订阅(事件总线)的更多相关文章

  1. Android 使用RxJava实现一个发布/订阅事件总线

    1.简单介绍 1.1.发布/订阅事件主要用于网络请求的回调. 事件总线可以使Android各组件之间的通信变得简单,而且可以解耦. 其实RxJava实现事件总线和EventBus比较类似,他们都依据与 ...

  2. RELabel : 一个极简的正则表达式匹配和展示框架

    html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...

  3. Spring Boot(5)一个极简且完整的后台框架

    https://blog.csdn.net/daleiwang/article/details/75007588 Spring Boot(5)一个极简且完整的后台框架 2017年07月12日 11:3 ...

  4. 借助腾讯云的云函数实现一个极简的API网关

    借助腾讯云的云函数实现一个极简的API网关 Intro 微信小程序的域名需要备案,但是没有大陆的服务器,而且觉得备案有些繁琐,起初做的小程序都有点想要放弃了,后来了解到腾讯云的云函数,于是利用腾讯云的 ...

  5. Vue.js 入门:从零开始做一个极简 To-Do 应用

    Vue.js 入门:从零开始做一个极简 To-Do 应用 写作时间:2019-12-10版本信息:Vue.js 2.6.10官网文档:https://cn.vuejs.org/ 前言  学习 Vue ...

  6. 【转】手摸手,带你用vue撸后台 系列四(vueAdmin 一个极简的后台基础模板)

    前言 做这个 vueAdmin-template 的主要原因是: vue-element-admin 这个项目的初衷是一个vue的管理后台集成方案,把平时用到的一些组件或者经验分享给大家,同时它也在不 ...

  7. 我喜欢的两个js类实现方式 现在再加上一个 极简主义法

    闭包实现 变量是不会变的:) var myApplication = function(){ var name = 'Yuri'; var age = '34'; var status = 'sing ...

  8. 使用apache-cxf-2.2.10来制作一个极简版WebService程序

    原想拿最新版cxf来制作的,无奈Apache的zip包总下不下来,国内的apache-cxf-2.2.10却一蹴而就,也就用了这个版本.下载地址是:http://pan.baidu.com/s/1td ...

  9. 一个极简的守护进程Bash脚本

    由于最近写的Node.js程序因为一些Bug,会出现一些自动退出的问题,所以需要在它退出的时候及时发现,并重新启动 于是查阅了些资料,写了一个Bash的程序,功能十分简单,就是每隔3s判断一次处在60 ...

  10. 飘逸的python - 实现一个极简的优先队列

    一个队列至少满足2个方法,put和get. 借助最小堆来实现. 这里按"值越大优先级越高"的顺序. #coding=utf-8 from heapq import heappush ...

随机推荐

  1. Python表格处理模块xlrd在Anaconda中的安装

      本文介绍在Anaconda环境下,安装Python读取.xls格式表格文件的库xlrd的方法.   xlrd是一个用于读取Excel文件的Python库,下面是xlrd库的一些主要特点和功能: 读 ...

  2. golang开发类库推荐

    gin star:74.6k 地址:https://github.com/gin-gonic/gin gin是最受开发者欢迎的 Web 框架,它有诸多的优点,性能高.轻量级和简洁的 API 设计,社区 ...

  3. #主席树,dsu on tree,树上倍增#洛谷 3302 [SDOI2013]森林

    题目 分析 第\(k\)大的问题可以用主席树解决, 下标为节点离散化后的权值, 连边操作考虑合并两棵树, 如果暴力合并那肯定会T飞,考虑启发式合并 同时维护子树内的lca,方便主席树的查询 代码 #i ...

  4. Python实现聊天机器人接口封装部署

    一.前言说明 博客声明:此文链接地址https://www.cnblogs.com/Vrapile/p/12427326.html,请尊重原创,未经允许禁止转载!!! 1. 功能简述 (1)将chat ...

  5. winrt新dx截图最小实现

    转自:https://stackoverflow.co/questions/11283015 效果还是很不错的 #include <iostream> #include <Windo ...

  6. 《Spring 测试指南》:JPA、MockMvc 和 @SpringBootTest 详解

    测试 Spring 提供了一组测试工具,可以轻松地测试 Spring 应用程序的各个组件,包括控制器.服务.存储库和其他组件.它具有丰富的测试注释.实用程序类和其他功能,以帮助进行单元测试.集成测试等 ...

  7. Python 中的数字类型与转换技巧

    Python中有三种数字类型: int(整数) float(浮点数) complex(复数) 当您将值分配给变量时,将创建数字类型的变量: 示例:获取您自己的Python服务器 x = 1 # int ...

  8. Redis 19 SpringBoot集成

    概述 SpringBoot 整合 Redis 是使用 SpringData 实现的. SpringData 是与 SpringBoot 齐名的顶级项目,整合了对常用数据库的模板型操作. 在 Sprin ...

  9. Qt线程简单使用一:QThread~创建线程类子类

      需求: 点击QPushButton按钮,QLabel中的数字,不断累加,一直到999.   做法: 点击QPushButton后,启动线程,线程while循环,不断发送累加的数字回主线程,修改QL ...

  10. opengauss-jdbc问题整理

    opengauss-jdbc问题整理(更新中) 问题 1 jdbc 批量执行 insert 语句时返回结果不符合 Spring jpa 预期 问题描述: jdbc 执行查询时,可以使用prepares ...