WinForm應用實戰開發指南 - 如何實現類似事件總線的消息處理?

MediatR是一款進程內的消息訂閱、發佈框架,可實現請求/響應、命令、查詢、通知和事件的消息傳遞,解耦了消息處理器和消息之間耦合。提供了Send方法用於發佈到單個處理程序、Publish方法發佈到多個處理程序,使用起來非常方便。目前支持 .NET Framework 、.NET Stardand、.NETCore等版本,可跨平臺使用。本文介紹在Winform系統開發中,使用MediatR來實現類似事件總線的消息處理。

PS:給大家推薦一個C#開發可以用到的界面組件——DevExpress WinForms,它能完美構建流暢、美觀且易於使用的應用程序,無論是Office風格的界面,還是分析處理大批量的業務數據,它都能輕鬆勝任!

安裝使用MediatR

MediatR的GitHub項目地址:https://github.com/jbogard/MediatR

MediatR的各種場景使用代碼:https://github.com/jbogard/MediatR/wiki

如果我們在VS開發項目,我們在Nugget上找到對應模塊,直接添加到項目引用即可,如下所示。

WinForm應用實戰開發指南 - 如何實現自定義用戶控件及自定義事件處理?

MediatR使用 Microsoft.Extensions.DependencyInjection.Abstractions 來 注入服務處理,我們使用MediatR的時候,首先需要構造ServiceCollection,然後添加配置到其中。

// IServiceCollection負責註冊
IServiceCollection services = new ServiceCollection();

//註冊MediatR服務,用於測試MediatR的服務
services.AddMediatR(cfg => {
cfg.RegisterServicesFromAssembly(typeof(Portal).Assembly);
});

使用注入服務的時候,我們需要獲得其中的ServiceProvider,如下通過BuildServiceProvider 獲得該對象。

IServiceProvider provider = services.BuildServiceProvider();

然後我們創建一個靜態類來存儲這個對象。

//存儲全局IServiceProvider的接口實例, 便於後續獲得接口實例
ServiceLocator.ConfigService(provider);

其中靜態類 ServiceLocator 的代碼如下所示。

/// <summary>
/// 全局存儲IServiceProvider
/// </summary>
public class ServiceLocator
{
/// <summary>
/// IOC中的IServiceProvider對象接口
/// </summary>
public static IServiceProvider SerivcePovider { get; private set; }

/// <summary>
/// 賦值IServiceProvider到靜態變量中
/// </summary>
/// <param name="provider">IServiceProvider對象接口</param>
public static void ConfigService(IServiceProvider provider)
{
SerivcePovider = provider;
}

/// <summary>
/// 獲取指定服務接口實例
/// </summary>
/// <returns></returns>
public static T GetService<T>()
{
return SerivcePovider.GetService<T>();
}
}

後面我們就可以通過該靜態類的 GetService<T>() 方法獲取對應的注入接口IMediator,需要利用該接口來發送Send請求/應答命令或者發佈Publish消息的處理。例如我們在窗體對象中定義該接口,用於實際的相關命令、消息的處理。

public partial class TestMediatR : BaseForm
{
private readonly IMediator _mediator;

public TestMediatR()
{
InitializeComponent();

_mediator = ServiceLocator.GetService<IMediator>();
}

MediatR命令或者消息的處理

MediatR是一個跨平臺通過一種進程內消息傳遞機制,進行請求/響應、命令、查詢、通知和事件的消息傳遞,並通過C#泛型來支持消息的智能調度,其目的是消息發送和消息處理的解耦。它支持以單播和多播形式使用同步或異步的模式來發布消息,創建和偵聽事件。它主要的幾個對象:

  • IMediator:主要提供Send與Publish方法,需要執行的命令都是通過這兩個方法實現。
  • IRequest:命令查詢 | 處理類所繼承的接口,一個有返回類型,一個無返回類型,一個查詢對應一個處理類,程序集只認第一個掃描到的類。
  • IRequestHandler(實現Handle方法) :命令處理接口。命令查詢 | 處理類繼承它,也可以繼承AsyncRequestHandler(實現抽象Handle方法)、RequestHandler(實現抽象Handle方法)接口。
  • INotification:命令查詢 | 處理類所繼承的接口這個沒有返回,與IRequest不通的是可以對於多個處理類。
  • INotificationHandler:與IRequestHandler一樣的只不過這是INotification的處理接口。

Request/Response模式對象定義

/// <summary>
/// 請求類
/// </summary>
public class RetrieveInfoCommandRequest : IRequest<RetrieveInfoCommandResponse>
{
public string Text { get; set; }
}
/// <summary>
/// 迴應消息
/// </summary>
public class RetrieveInfoCommandResponse
{
public string OutputMessage { get; set; }
}

/// <summary>
/// 請求應答處理類
/// </summary>
public class RetrieveInfoCommandHandler : IRequestHandler<RetrieveInfoCommandRequest, RetrieveInfoCommandResponse>
{
public async Task<RetrieveInfoCommandResponse> Handle(RetrieveInfoCommandRequest request, CancellationToken cancellationToken)
{
var response = new RetrieveInfoCommandResponse();
response.OutputMessage = $"This is an example of MediatR using {request.Text}";
return response;
}
}

例如我們根據這個請求、應答的消息協議,以及定義的處理Handler類(唯一一個),可以設計一個Winform界面來測試消息的處理。

WinForm應用實戰開發指南 - 如何實現自定義用戶控件及自定義事件處理?

界面的代碼如下所示。

/// <summary>
/// 測試MediatR的窗體例子
/// </summary>
public partial class TestMediatR : BaseForm
{
private readonly IMediator _mediator;

public TestMediatR()
{
InitializeComponent();

_mediator = ServiceLocator.GetService<IMediator>();
}

/// <summary>
/// 使用請求、應答的消息進行測試,獲得返回結果後輸出顯示
/// </summary>
private async void btnSend_Click(object sender, EventArgs e)
{
//應答處理
var outputMessage = await _mediator.Send(new RetrieveInfoCommandRequest
{
Text = this.txtSend.Text
});
Console.WriteLine(outputMessage.OutputMessage);
this.txtReceived.AppendText(outputMessage.OutputMessage + Environment.NewLine);
}

上面的命令消息方式,有返回值,如果不需要返回值,也可以採用這種一一應答的方式,那麼定義的時候,繼承IRequest接口即可。

public class OneWay : IRequest { }
public class OneWayHandler : IRequestHandler<OneWay>
{
public Task Handle(OneWay request, CancellationToken cancellationToken)
{
// do work
return Task.CompletedTask;
}
}

Notification 消息通知模式

如果我們需要類似事件多播的處理,也就是常規的消息通知處理,採用INotification方式。

Notification模式將消息發佈給多個處理程序,消息的處理沒有返回值。

/// <summary>
/// 通知類
/// </summary>
public class MyNotification : INotification
{
public string Message { get; }

public MyNotification(string message)
{
Message = message;
}
}

/// <summary>
/// Notification處理程序-模塊1
/// </summary>
public class MyNotifyHandler : INotificationHandler<MyNotification>
{
public Task Handle(MyNotification notification, CancellationToken cancellationToken)
{
var message = "模塊1-收到消息:" + notification.Message;
//MessageDxUtil.ShowTips(message);

//提示消息
var alert = new AlertControl();
alert.FormLocation = AlertFormLocation.TopRight;
alert.AutoFormDelay = 3000;
alert.Show(Portal.gc.MainDialog, message, message);

// 處理通知
Console.WriteLine($"Notification處理程序-模塊1-收到消息: {notification.Message}");
return Task.CompletedTask;
}
}
/// <summary>
/// Notification處理程序-模塊2
/// </summary>
public class MySecondNotifyHandler : INotificationHandler<MyNotification>
{
public Task Handle(MyNotification notification, CancellationToken cancellationToken)
{
var message = "模塊2-收到消息:" + notification.Message;
//MessageDxUtil.ShowTips(message);

//提示消息
var alert = new AlertControl();
alert.FormLocation = AlertFormLocation.TopRight;
alert.AutoFormDelay = 3000;
alert.Show(Portal.gc.MainDialog, message, message);

// 處理通知
Console.WriteLine($"Notification處理程序-模塊2-收到消息: {notification.Message}");
return Task.CompletedTask;
}
}

我們在界面上發佈消息的代碼如下所示。

private async void btnNotify_Click(object sender, EventArgs e)
{
//發佈消息
await _mediator.Publish(new MyNotification(this.txtSend.Text));
}

可以看到在控制檯和UI上我們的都有測試消息的輸出。

WinForm應用實戰開發指南 - 如何實現自定義用戶控件及自定義事件處理?
WinForm應用實戰開發指南 - 如何實現自定義用戶控件及自定義事件處理?

默認情況下,MediatR的消息發佈是一個一個執行的,即便是返回Task的情況,也是使用await等待上一個執行完成後才進行下一個的調用。如果需要使用並行的方法進行調用,可以進行定製,具體可參考官方示例:MediatR.Examples.PublishStrategies

對於MediatR來說,無論是發送IRequest類型消息,還是發佈INotification類型消息,都是異步的。這裏需要特別留意,即使你使用的是同步的消息處理程序,對於消息發佈來說,都是異步的,與你的處理程序是同步或異步無關。

詳細的介紹,可以參考官方的案例介紹:https://github.com/jbogard/MediatR/wiki

回顧WPF的MVVM的消息處理

對於WPF,其實也是類似採用該組件實現事件、消息的處理的,不過如果我們採用MVVM的框架設計模式,可以採用MVVM(微軟的 CommunityToolkit.Mvvm的組件包)的內置的消息處理模式。

CommunityToolkit.Mvvm (又名 MVVM 工具包,以前名爲 Microsoft.Toolkit.Mvvm) 是一個現代、快速且模塊化的 MVVM 庫。官網介紹地址:https://learn.microsoft.com/zh-cn/dotnet/communitytoolkit/mvvm/

利用MVVM推送一條消息,如下代碼所示。

//發送MVVM消息信息通知方式(一)
WeakReferenceMessenger.Default.Send(new ClickEventMessage(eventData));

而其中 ClickEventMessage 是我們根據要求定義的一個消息對象類,如下代碼所示。

WinForm應用實戰開發指南 - 如何實現自定義用戶控件及自定義事件處理?

完整的Command命令如下所示。

/// <summary>
/// 雙擊觸發MVVM消息通知
/// </summary>
/// <param name="typeName">處理類型:Number、Animal、WuHan</param>
/// <returns></returns>
[RelayCommand]
private async Task DoubleClick(string typeName)
{
var clickType = ClickEventType.Number;
var clickValue = this.Number;

..............//處理不同typeName值邏輯//事件數據
var eventData = new ClickEventData(clickType, clickValue);

//發送MVVM消息信息通知方式(一)
WeakReferenceMessenger.Default.Send(new ClickEventMessage(eventData));
}

通過這樣的消息發送,就需要有個地方來接收這個信息的,我們在需要處理事件的父窗口中攔截處理消息即可。

//處理MVVM的消息通知
WeakReferenceMessenger.Default.Register<ClickEventMessage>(this, (r, m) =>
{
var data = m.Value;
var list = ControlHelper.FindVisualChildren<LotteryItemControl>(this.listControl);
foreach (var lottery in list)
{
lottery.SetSelected(data);
}
});

從而實現了WPF消息的發送和應答處理。

本文轉載自:博客園 - 伍華聰

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章