ABP框架中的事件總線功能介紹
事件總線
關於事件總線
ABP 中,為瞭方便進程間通訊,給開發者提供瞭一個叫 事件總線
的功能,事件總線分為 本地事件總線
、分佈式事件總線
,本篇文章講的是 本地事件總線
,系列教程中暫時不考慮講解 分佈式事件總線
。
事件總線
需要使用 Volo.Abp.EventBus
庫,ABP 包中自帶,不需要額外引入。
事件總線是通過 訂閱-發佈 形式使用的,某一方隻需要按照格式推送事件,而不需要關註是誰接收瞭事件和如何處理事件。
你可以參考官方文檔:https://docs.abp.io/zh-Hans/abp/latest/Local-Event-Bus
為什麼需要這個東西
首先列舉一下,你工作開發的項目中,編寫 控制器時,是不是有這幾種代碼。
// 記錄日志 1 Task.Run(()=> { _apiLog.Info($"xxxxxxxx"); });
// 記錄日志 2 catch(Exception ex) { _apiLog.Error(ex); }
// 記錄日志 3 _apiLog.Info($"登陸信息:用戶 [{userName}({clientAdrress})]\);
筆者認為,改善的上述問的方法之一是將函數的功能跟記錄日志分開,函數執行任務時,隻需要把狀態和信息通過事件總線推送,而不需要瞭關註應該如何處理這些內容。
另外,還有當函數執行某些步驟時,產生瞭事件,開發者喜歡 new Thread
一個新的線程去執行別的任務,或者 Task.Run
。
其實,通過事件總線,我們更加好地隔離代碼,遵從 單一職責原則
。當然還有很多方面值得使用事件總線,這裡我們就不再扯淡瞭。
前面,我們編寫瞭全局異常攔截器,還有日志組件,這一篇我們將通過事件總線,將 Web 程序的一些部件組合起來。
事件總線創建過程
訂閱事件
創建一個服務來訂閱事件,當程序中發生某種事件時,此服務將被調用。
事件服務必須繼承 ILocalEventHandler<in TEvent>
接口,並實現以下函數:
Task HandleEventAsync(TEvent eventData);
一個系統中,事件服務可以有多個,每個服務的 TEvent
類型不能相同,因為 TEvent
的類型是調用服務的標識。當發生 TEvent
事件後,系統通過 TEvent
去找到這個服務。
事件服務創建完畢後,需要加入到依賴註入中,你可以多繼承一個 ITransientDependency
接口,然後統一掃描程序集加入到 依賴註入容器中(第三篇提到過)。
事件
即上面提到的 TEvent
。
假設有一個系統中所有的事件服務都放到一個容器中,發佈者隻能傳遞一個事件,而不能指定誰來提供響應服務。
容器是通過 TEvent
來查找服務的。
事件就是一個模型類,也可以使用 int
或者 string
等簡單類型(請不要用簡單類型做事件),用於傳遞信息。
一般使用 Event
做後綴。
發佈事件
如果需要發佈一個事件,隻需要註入 ILocalEventBus
即可。
private readonly ILocalEventBus _localEventBus; public MyService(ILocalEventBus localEventBus) { _localEventBus = localEventBus; }
然後發佈事件:
await _localEventBus.PublishAsync( new TEvent { ... ... } );
全局異常加入事件總線功能
創建事件
在 AbpBase.Web
中,創建一個 Handlers
目錄,再在 Handlers
目錄下,創建 HandlerEvents
目錄。
然後在 HandlerEvents
目錄,創建一個 CustomerExceptionEvent.cs
文件。
CustomerExceptionEvent
作為一個異常事件,用於傳遞異常的信息,而不僅僅是將 Exception ex
記錄就瞭事。
其文件內容如下:
using System; using System.Collections.Generic; using System.Reflection; using System.Text; namespace AbpBase.Application.Handlers.HandlerEvents { /// <summary> /// 全局異常推送事件 /// </summary> public class CustomerExceptionEvent { /// <summary> /// 隻記錄異常 /// </summary> /// <param name="ex"></param> public CustomerExceptionEvent(Exception ex) { Exception = ex; } /// <summary> /// 此異常發生時,用戶請求的路由地址 /// </summary> /// <param name="ex"></param> /// <param name="actionRoute"></param> public CustomerExceptionEvent(Exception ex, string actionRoute) { Exception = ex; Action = actionRoute; } /// <summary> /// 此異常發生在哪個類型的方法中 /// </summary> /// <param name="ex"></param> /// <param name="method"></param> public CustomerExceptionEvent(Exception ex, MethodBase method) { Exception = ex; MethodInfo = (MethodInfo)method; } /// <summary> /// 記錄異常信息 /// </summary> /// <param name="ex"></param> /// <param name="actionRoute"></param> /// <param name="method"></param> public CustomerExceptionEvent(Exception ex, string actionRoute, MethodBase method) { Exception = ex; Action = actionRoute; MethodInfo = (MethodInfo)method; } /// <summary> /// 當前出現位置 /// <example> /// <code> /// MethodInfo = (MethodInfo)MethodBase.GetCurrentMethod(); /// </code> /// </example> /// </summary> public MethodInfo MethodInfo { get; private set; } /// <summary> /// 發生異常的 Action /// </summary> public string Action { get; private set; } /// <summary> /// 具體異常 /// </summary> public Exception Exception { get; private set; } } }
訂閱事件
訂閱事件,即將其定義為事件的響應者、服務提供者。
當異常發生後,異常的位置,推送異常信息,那麼誰來處理這些信息呢?是訂閱者。
這裡我們定義一個異常日志處理類,來處理程序推送的異常信息。
在 AbpBase.Web
項目的 Handlers
目錄中,添加一個 CustomerExceptionHandler
類,繼承:
public class CustomerExceptionHandler : ILocalEventHandler<CustomerExceptionEvent>, ITransientDependency
服務要處理事件,必須繼承 ILocalEventHandler<T>
,而 ITransientDependency
是為瞭此服務可以可以自動註入到容器中。
其文件內容如下:
using AbpBase.Application.Handlers.HandlerEvents; using Serilog; using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus; namespace AbpBase.Application.Handlers { /// <summary> /// 全局異常記錄日志 /// </summary> public class CustomerExceptionHandler : ILocalEventHandler<CustomerExceptionEvent>, ITransientDependency { private readonly ILogger _ILogger; public CustomerExceptionHandler(ILogger logger) { _ILogger = logger; } public async Task HandleEventAsync(CustomerExceptionEvent eventData) { StringBuilder stringBuilder = new StringBuilder(256); stringBuilder.AppendLine(); stringBuilder.Append("Action: "); stringBuilder.AppendLine(eventData.Action); if (eventData.MethodInfo != null) { stringBuilder.Append("Class-Method: "); stringBuilder.Append(eventData.MethodInfo?.DeclaringType.FullName); stringBuilder.AppendLine(eventData.MethodInfo?.Name); } stringBuilder.Append("Source: "); stringBuilder.AppendLine(eventData.Exception.Source); stringBuilder.Append("TargetSite: "); stringBuilder.AppendLine(eventData.Exception.TargetSite?.ToString()); stringBuilder.Append("InnerException: "); stringBuilder.AppendLine(eventData.Exception.InnerException?.ToString()); stringBuilder.Append("Message: "); stringBuilder.AppendLine(eventData.Exception.Message); stringBuilder.Append("HelpLink: "); stringBuilder.AppendLine(eventData.Exception.HelpLink); _ILogger.Fatal(stringBuilder.ToString()); await Task.CompletedTask; } } }
這樣寫,記錄的日志可以有很好的層次結構。
發佈事件
定義瞭事件的格式和定義服務來訂閱事件後,我們來創建一個發佈者。
我們修改一下 WebGlobalExceptionFilter
。
增加依賴註入:
private readonly ILocalEventBus _localEventBus; public WebGlobalExceptionFilter(ILocalEventBus localEventBus) { _localEventBus = localEventBus; }
發佈事件:
public async Task OnExceptionAsync(ExceptionContext context) { if (!context.ExceptionHandled) { await _localEventBus.PublishAsync(new CustomerExceptionEvent(context.Exception, context.ActionDescriptor?.DisplayName)); ... ...
測試
創建一個 Action :
[HttpGet("/T4")] public string MyWebApi4() { int a = 1; int b = 0; int c = a / b; return c.ToString(); }
然後訪問 https://localhost:5001/T4 ,會發現請求後報錯
在 AbpBase.Web
的 Logs
目錄中,打開 -Fatal.txt
文件。
可以看到:
2020-09-16 18:49:27.750 +08:00 [FTL] Action: ApbBase.HttpApi.Controllers.TestController.MyWebApi4 (ApbBase.HttpApi) Source: ApbBase.HttpApi TargetSite: System.String MyWebApi4() InnerException: Message: Attempted to divide by zero. HelpLink:
除瞭異常信息外,我們還可以很方便的知道異常發生在 TestController.MyWebApi4
這個位置。
記錄事件
如果在普通方法裡面出現異常,我們這樣這樣記錄:
catch (Exception ex) { ... new CustomerExceptionEvent(ex, MethodBase.GetCurrentMethod()); ... }
MethodBase.GetCurrentMethod()
可以獲取當前正在運行的方法,獲得信息後將此參數傳遞給異常記錄服務,會自動解析出具體是哪個地方發生異常。
由於目前 Web 程序中還沒有編寫什麼服務,因此我們先結合到異常日志功能中,後面編寫服務時,會再次用到事件總線。
完整代碼參考:https://github.com/whuanle/AbpBaseStruct/tree/master/src/4/AbpBase
源碼地址:https://github.com/whuanle/AbpBaseStruct
到此這篇關於ABP框架中的事件總線功能的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- 為ABP框架配置數據庫
- C# 基於消息發佈訂閱模型的示例(上)
- 基於ABP框架實現數據字典開發
- C#中使用jieba.NET、WordCloudSharp制作詞雲圖的步驟
- C# 字符串與unicode互相轉換實戰案例