• 在Winform系统开发中,使用MediatR来实现类似事件总线的消息处理


    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 的代码如下所示。

    复制代码
        /// 
        /// 全局存储IServiceProvider
        /// 
        public class ServiceLocator
        {
            /// 
            /// IOC中的IServiceProvider对象接口
            /// 
            public static IServiceProvider SerivcePovider { get; private set; }
    
            /// 
            /// 赋值IServiceProvider到静态变量中
            /// 
            /// IServiceProvider对象接口
            public static void ConfigService(IServiceProvider provider)
            {
                SerivcePovider = provider;
            }
    
            /// 
            /// 获取指定服务接口实例
            /// 
            /// 
            public static T GetService()
            {
                return SerivcePovider.GetService();
            }
        }
    复制代码

    后面我们就可以通过该静态类的 GetService() 方法获取对应的注入接口IMediator,我们需要利用该接口来发送Send请求/应答命令或者发布Publish消息的处理。例如我们在窗体对象中定义该接口,用于实际的相关命令、消息的处理。

    复制代码
        public partial class TestMediatR : BaseForm
        {
            private readonly IMediator _mediator;
    
            public TestMediatR()
            {
                InitializeComponent();
    
                _mediator = ServiceLocator.GetService();
            }
    复制代码

     

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

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

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

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

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

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

      INotificationHandler:与IRequestHandler一样的只不过这是INotification的处理接口。

    Request/Response模式对象定义

    复制代码
        /// 
        /// 请求类
        /// 
        public class RetrieveInfoCommandRequest : IRequest
        {
            public string Text { get; set; }
        }
        /// 
        /// 回应消息
        /// 
        public class RetrieveInfoCommandResponse
        {
            public string OutputMessage { get; set; }
        }
    
        /// 
        /// 请求应答处理类
        /// 
        public class RetrieveInfoCommandHandler : IRequestHandler
        {
            public async Task Handle(RetrieveInfoCommandRequest request, CancellationToken cancellationToken)
            {
                var response = new RetrieveInfoCommandResponse();
                response.OutputMessage = $"This is an example of MediatR using {request.Text}";
                return response;
            }
        }
    复制代码

    例如我们根据这个请求、应答的消息协议,以及定义的处理Handler类(唯一一个),我们可以设计一个Winform界面来测试消息的处理。

     界面的代码如下所示。

    复制代码
    /// 
    /// 测试MediatR的窗体例子
    /// 
    public partial class TestMediatR : BaseForm
    {
        private readonly IMediator _mediator;
    
        public TestMediatR()
        {
            InitializeComponent();
    
            _mediator = ServiceLocator.GetService();
        }
    
        /// 
        /// 使用请求、应答的消息进行测试,获得返回结果后输出显示
        /// 
        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
        {
            public Task Handle(OneWay request, CancellationToken cancellationToken)
            {
                // do work
                return Task.CompletedTask;
            }
        }
    复制代码

     

    Notification 消息通知模式

    如果我们需要类似事件多播的处理,也就是常规的消息通知处理,我们采用INotification方式。

    Notification模式将消息发布给多个处理程序,消息的处理没有返回值。

    复制代码
    /// 
    /// 通知类
    /// 
    public class MyNotification : INotification
    {
        public string Message { get; }
    
        public MyNotification(string message)
        {
            Message = message;
        }
    }
    
    /// 
    /// Notification处理程序-模块1
    /// 
    public class MyNotifyHandler : INotificationHandler
    {
        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;
        }
    }
    /// 
    /// Notification处理程序-模块2
    /// 
    public class MySecondNotifyHandler : INotificationHandler
    {
        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命令如下所示。

    复制代码
    /// 
    /// 双击触发MVVM消息通知
    /// 
    /// 处理类型:Number、Animal、WuHan
    /// 
    [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(this, (r, m) =>
    {
        var data = m.Value;
        var list = ControlHelper.FindVisualChildren(this.listControl);
        foreach (var lottery in list)
        {
            lottery.SetSelected(data);
        }
    });
    复制代码

    从而实现了WPF消息的发送和应答处理。

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

     

  • 相关阅读:
    分享几种 Java8 中通过 Stream 对列表进行去重的方法
    JavaScript—分支结构和循环结构整理
    企业架构LNMP学习笔记23
    MyBatisPlus-标准数据层CRUD功能制作
    AQS之ReentrantLock分析 (五)
    PCIE下载的驱动安装
    m基于PTS+TR的OFDM系统PAPR联合抑制算法matlab仿真
    叁[3],感兴趣区域ROI
    基于PETALINUX的以太网调试
    【算法挨揍日记】day14——724. 寻找数组的中心下标、238. 除自身以外数组的乘积
  • 原文地址:https://www.cnblogs.com/wuhuacong/p/17984987