在Biwen.QuickApi中整合一個極簡的發佈訂閱(事件匯流排)

来源:https://www.cnblogs.com/vipwan/p/18184088
-Advertisement-
Play Games

閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...


閑來無聊在我的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 subscriberType in EventSubscribers)
            {
                //存在一個訂閱者訂閱多個事件的情況:
                var baseTypes = subscriberType.GetInterfaces().Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == InterfaceEventSubscriber).ToArray();
                foreach (var baseType in baseTypes)
                {
                    services.AddScoped(baseType, subscriberType);
                }
            }
            //註冊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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 大家好,我是R哥。 金三銀四結束了,上個月分享了一個 35K 入職的面試輔導案例: 35K*14 薪入職了,這公司只要不裁員,我能一直呆下去。。 今天再分享一個上個月讓人很有成就感的面試輔導 case: 外包、空窗四個月、薪資 10k、996 ——> 甲方公司、薪資15k、早九晚六(WLB),從報名 ...
  • 當一個線程被啟動後,如果再次調start()方法,將會拋出IllegalThreadStateException異常。 這是因為Java線程的生命周期只有一次。調用start()方法會導致系統在新線程中運行執行體,但是如果線程已經結束,則不能再次使用,需要重新創建一個新的線程對象並調用start()... ...
  • 正文 昨天玩到了凌晨 3 點,今天睡了一天…… 斷斷續續睡到 12 點起床,下午又從 5 點睡到了 7 點。我願稱之為睡神……. 其它時間就是做工作日一直沒時間做的雜事,比如洗衣服,刷鞋,換洗被套什麼的,還挺花時間。用了得有兩三個小時。 所以昨天說的今天開擺,那是真的開擺了 (笑。 現在晃一下頭,能 ...
  • 1. Spring6 的JdbcTemplate的JDBC模板類的詳細使用說明 @目錄1. Spring6 的JdbcTemplate的JDBC模板類的詳細使用說明每博一文案2. 環境準備3. 數據準備4. 開始4.1 從數據表中插入(添加)數據4.2 從數據表中修改數據4.3 從數據表中刪除數據4 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
一周排行
    -Advertisement-
    Play Games
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...