MediatR是一款进程内的消息订阅、发布框架,可实现请求/响应、命令、查询、通知和事件的消息传递,解耦了消息处理器和消息之间耦合。提供了Send方法用于发布到单个处理程序、Publish方法发布到多个处理程序,使用起来非常方便。目前支持 .NET Framework 、.NET Stardand、.NETCore等版本,可跨平台使用。本篇随笔介绍在Winform系统开发中,使用MediatR来实现类似事件总线的消息处理。

1、安装使用MediatR

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

MediatR的各种场景使用代码:https://github.com/jbogard/MediatR/wiki

如果我们在VS开发项目,我们在Nugget上找到对应模块,直接添加到项目引用即可,如下所示。

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>();
}

2、MediatR命令或者消息的处理

MediatR是一个跨平台通过一种进程内消息传递机制,进行请求/响应、命令、查询、通知和事件的消息传递,并通过C#泛型来支持消息的智能调度,其目的是消息发送和消息处理的解耦。它支持以单播和多播形式使用同步或异步的模式来发布消息,创建和侦听事件。它主要的几个对象:

  IMediator:主要提供Send与Publish方法,需要执行的命令都是通过这两个方法实现

  IRequest、IRequest<T>命令查询 | 处理类所继承的接口,一个有返回类型,一个无返回类型,一个查询对应一个处理类,程序集只认第一个扫描到的类。

  IRequestHandler<in TRequest,TResponse>(实现Handle方法) :命令处理接口。命令查询 | 处理类继承它,也可以继承AsyncRequestHandler(实现抽象Handle方法)、RequestHandler(实现抽象Handle方法)接口

  INotification命令查询 | 处理类所继承的接口这个没有返回,与IRequest不通的是可以对于多个处理类。

  INotificationHandler<in TNotification>:与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界面来测试消息的处理。

界面的代码如下所示。

/// <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上我们的都有测试消息的输出。

默认情况下,MediatR的消息发布是一个一个执行的,即便是返回Task的情况,也是使用await等待上一个执行完成后才进行下一个的调用。如果需要使用并行的方法进行调用,可以进行定制,具体可参考官方示例:MediatR.Examples.PublishStrategies

对于MediatR来说,无论是发送IRequest类型消息,还是发布INotification类型消息,都是异步的。这里需要特别留意,即使你使用的是同步的消息处理程序,对于消息发布来说,都是异步的,与你的处理程序是同步或异步无关。

详细的介绍,可以参考官方的案例介绍:https://github.com/jbogard/MediatR/wiki

3、回顾WPF的MVVM的消息处理

对于WPF,其实也是类似采用该组件实现事件、消息的处理的,不过如果我们采用MVVM的框架设计模式,可以采用MVVM(微软的 CommunityToolkit.Mvvm的组件包)的内置的消息处理模式,我在随笔《使用WPF开发自定义用户控件,以及实现相关自定义事件的处理》有相关的介绍。

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 是我们根据要求定义的一个消息对象类,如下代码所示。

完整的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消息的发送和应答处理。

另外,我在随笔《使用 FastEndpoints 来垂直切割Web API的控制器方法》介绍的FastEndpoints 处理机制,也是类似这样的模式,有兴趣可以了解一下FastEndpoints 的处理。

在Winform系统开发中,使用MediatR来实现类似事件总线的消息处理的更多相关文章

  1. 系统开发中按下Enter键登录系统

    转载来自:http://www.jb51.net/article/54308.htm 系统开发中按下Enter键登录系统,即就是监听键盘,当按下Enter键后调用登录按钮的click()事件. JS方 ...

  2. C、C++、boost、Qt在嵌入式系统开发中的使用

    概述 嵌入式系统开发相对来说属于偏底层的开发,也就是与硬件结合比较紧密,只能使用C/C++语言.对于做平台开发的人来说,C语言真的是很"古老"的语言,属于操作系统语言!好多人会觉得 ...

  3. 二十八、带给我们一种新的编码思路——EFW框架CS系统开发中的MVC模式探讨

    回<[开源]EFW框架系列文章索引>        EFW框架源代码下载V1.3:http://pan.baidu.com/s/1c0dADO0 EFW框架实例源代码下载:http://p ...

  4. 二十七、EFW框架BS系统开发中的MVC模式探讨

    回<[开源]EFW框架系列文章索引>        EFW框架源代码下载V1.3:http://pan.baidu.com/s/1c0dADO0 EFW框架实例源代码下载:http://p ...

  5. 浅谈Android系统开发中LOG的使用

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6581828 在程序开发过程中,LOG是广泛使用 ...

  6. 系统开发中使用拦截器校验是否登录并使用MD5对用户登录密码进行加密

    项目名称:客户管理系统 项目描述: 项目基于javaEE平台,B/S模式开发.使用Struts2.Hibernate/Spring进行项目框架搭建.使用Struts中的Action 控制器进行用户访问 ...

  7. 浅谈Android系统开发中LOG的使用【转】

    本文转载自:http://blog.csdn.net/luoshengyang/article/details/6581828 在程序开发过程中,LOG是广泛使用的用来记录程序执行过程的机制,它既可以 ...

  8. 苹果系统开发中的混合编程(2):Swift和C的相互调用

      在进行Swift和C之间的相互调用时,有必要先了解一下两种语言之间的类型转换关系:   C 类型 Swift 类型 bool CBool char, signed char CChar unsig ...

  9. 苹果系统开发中的混合编程(1):Objective-C和C++的相互调用

    首先是OC调用C++的代码.   创建一个Objective-C的项目,并创建c++文件MyCppFile.hpp和MyCppFile.cpp.   把要调用Cpp代码的文件名改成mm后缀名,项目代码 ...

  10. 在Winform系统界面中对进展阶段的动态展示和处理

    在我们做客户关系管理系统的Winform界面的时候,需要对进展阶段这个属性进行一个方便的动态切换和标记处理,如我们根据不同的进展阶段显示不同的相关信息,也可以随时保存当前的阶段信息.其实也是一个比较常 ...

随机推荐

  1. 关于windows激活程序的木马病毒分析及处置方法

    客户电脑中毒,锁定几个病毒进程.EDR杀毒.木马专杀工具无法处置,该现象是和深信服外网AF防火墙联动后发现的行为,EDR无感知. 该病毒特征为,每日早上用户开机,均检查到外链du.testjj.com ...

  2. 教你几个部署多个nginx-ingress的注意事项

    本文分享自华为云社区<nginx-ingress工作原理以及多nginx-ingress部署注意事项>,作者: 可以交个朋友. 一.nginx-ingress工作原理 nginx-ingr ...

  3. 资源成本降低70%!华为MetaERP资产核算的Serverless架构实践

    本文分享自华为云社区<资源成本降低70%!华为MetaERP资产核算的Serverless架构实践>,作者: 华为云PaaS服务小智. 资产核算是指在一定的财务周期,对企业拥有的房屋建筑物 ...

  4. 【API进阶之路】太秃然了,老板要我一周内检测并导入一万个小时的视频

    摘要:假期结束后回来上班,走进电梯都有一种特别的感觉,电梯那个植发广告里的大哥看我的眼神好像和之前不太一样- 上回说到,老板奖励7天带薪假,我就回家玩耍了几天,顺便还帮兄弟发不脱当了一回"A ...

  5. API生态的发展与机遇:从5000组数据看中国API生态与开发者现状

    摘要:华为云联合多家单位发布了<中国API生态与开发者现状调研报告(2020年)>,旨在通过API生态.API开发者.使用者.API全生命周期管理等多视角展现我国API发展的现状与机遇,力 ...

  6. 如何做好分支管理,保证高效CI/CD?

    摘要:一文讲述git分支管理策略. 本文分享自华为云社区<如何做好分支管理,保证高效CICD?>,作者:华为云PaaS服务小智. 引言 CI/CD是DevOps 的基础核心,做好CI/CD ...

  7. 讲透学烂二叉树(三):二叉树的遍历图解算法步骤及JS代码

    二叉树的遍历是指不重复地访问二叉树中所有结点,主要指非空二叉树,对于空二叉树则结束返回. 二叉树的遍历分为 深度优先遍历 先序遍历:根节点->左子树->右子树(根左右),有的叫:前序遍历 ...

  8. Chrome 英文翻译插件,沙拉查词

    下载: https://pan.baidu.com/s/1VRaZzKgyPKc0Qt6Gufk3yw 提取码: chbm 下载: https://pan.baidu.com/s/1VRaZzKgyP ...

  9. API 设计最佳实践(简版)

    Restful API 本文简称API,是一个种面向资源的架构.在Restful中一个API对应一个资源,资源可以是文本,图片,视频等.API特征有如下: 每一个URI代表一种资源 客户端和服务器之间 ...

  10. HTML5 postMessage 跨域跨窗口传递消息

    父页面代码: <!DOCTYPE html> <html> <head> <title>选择位置demo</title> <meta ...