百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

在Avalonia项目中使用MediatR和MS.DI库实现事件驱动通信

ccwgpt 2024-09-27 07:19 29 浏览 0 评论

大家好,我是沙漠尽头的狼!

AvaloniaUI是一个强大的跨平台.NET客户端开发框架,让开发者能够针对Windows、Linux、macOS、Android和iOS等多个平台构建应用程序。在构建复杂的应用程序时,模块化和组件间的通信变得尤为重要。Prism框架提供了模块化的开发方式,支持插件的热拔插,而MediatR则是一个实现了中介者(Mediator)模式的事件订阅发布框架,非常适合用于模块之间以及模块与主程序之间的通信。

本文重点是介绍MediatR,它 是 .NET 中的开源简单中介者模式实现。它通过一种进程内消息传递机制(无其他外部依赖),进行请求/响应、命令、查询、通知和事件的消息传递,并通过泛型来支持消息的智能调度。开源库地址是 https://github.com/jbogard/MediatR。

本文将详细介绍如何在Avalonia项目中使用MediatR和Microsoft的依赖注入(MS.DI)库来实现事件驱动的通信。

0. 基础知识准备-MediatR的基本用法

MediatR中有两种消息传递的方式:

  • Request/Response,用于一个单独的Handler。
  • Notification,用于多个Handler。

Request/Response

Request/Response 有点类似于 HTTP 的 Request/Response,发出一个 Request 会得到一个 Response。

Request 消息在 MediatR 中,有两种类型:

  • IRequest<T> 返回一个T类型的值。
  • IRequest 不返回值。

对于每个 request 类型,都有相应的 handler 接口:

  • IRequestHandler<T, U> 实现该接口并返回 Task<U>
  • RequestHandler<T, U> 继承该类并返回 U
  • IRequestHandler<T> 实现该接口并返回 Task<Unit>
  • AsyncRequestHandler<T> 继承该类并返回 Task
  • RequestHandler<T> 继承该类不返回

Notification

Notification 就是通知,调用者发出一次,然后可以有多个处理者参与处理。

1. 准备工作

首先,确保你的Avalonia项目中已经安装了必要的NuGet包。你将需要Prism.DryIoc.Avalonia作为依赖注入容器,以及MediatR来处理事件的发布和订阅。此外,为了将MediatR集成到DryIoc容器中,你还需要DryIoc.Microsoft.DependencyInjection包(这里感谢网友提供的技术解答)。

在项目的.csproj文件或NuGet包管理器中添加以下引用:

<PackageReference Include="Prism.DryIoc.Avalonia" Version="8.1.97.11072" /> 
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="8.0.0-preview-01" />

2. 配置容器和注册服务

在Avalonia项目中,你需要配置DryIoc容器以使用Microsoft的DI扩展,并注册MediatR服务。这通常在你的主启动类(如App.axaml.cs)中完成。

以下是配置容器和注册服务的示例代码:

namespace CodeWF.Tools.Desktop;

public class App : PrismApplication
{
// 省略了模块注入等和主题无关的代码,有兴趣源码在文末可查

/// <summary>
/// 1、DryIoc.Microsoft.DependencyInjection低版本可不要这个方法(5.1.0及以下)
/// 2、高版本必须,否则会抛出异常:System.MissingMethodException:“Method not found: 'DryIoc.Rules DryIoc.Rules.WithoutFastExpressionCompiler()'.”
/// 参考issues:https://github.com/dadhi/DryIoc/issues/529
/// </summary>
/// <returns></returns>
protected override Rules CreateContainerRules()
{
return Rules.Default.WithConcreteTypeDynamicRegistrations(reuse: Reuse.Transient)
.With(Made.Of(FactoryMethod.ConstructorWithResolvableArguments))
.WithFuncAndLazyWithoutRegistration()
.WithTrackingDisposableTransients()
//.WithoutFastExpressionCompiler()
.WithFactorySelector(Rules.SelectLastRegisteredFactory());
}

protected override IContainerExtension CreateContainerExtension()
{
IContainer container = new Container(CreateContainerRules());
container.WithDependencyInjectionAdapter();

return new DryIocContainerExtension(container);
}

protected override void RegisterRequiredTypes(IContainerRegistry containerRegistry)
{
base.RegisterRequiredTypes(containerRegistry);

IServiceCollection services = ConfigureServices();

IContainer container = ((IContainerExtension<IContainer>)containerRegistry).Instance;

container.Populate(services);
}

private static ServiceCollection ConfigureServices()
{
var services = new ServiceCollection();

// 注入MediatR
var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();

// 添加模块注入,未显示调用模块类型前,模块程序集是未加载到当前程序域`AppDomain.CurrentDomain`的
var assembly = typeof(SlugifyStringModule).GetAssembly();
assemblies.Add(assembly);
services.AddMediatR(configure =>
{
configure.RegisterServicesFromAssemblies(assemblies.ToArray());
});

return services;
}
}

在上面的代码中,我们重写了CreateContainerRulesCreateContainerExtensionRegisterRequiredTypes方法以配置DryIoc容器,并注册了MediatR服务和相关处理程序。

注意,在注册MediatR服务时,我们从当前已加载的程序集列表中查找并注册处理程序。如果模块是按需加载的,请确保在注册处理程序之前已加载了相应的模块。

此外,我们还演示了如何手动添加模块程序集到列表中以便注册处理程序。这通常在你需要显式控制哪些模块和处理程序被注册时很有用。但是,请注意,在大多数情况下,你可能希望使用更自动化的方式来加载和注册模块及处理程序(例如,通过扫描特定目录或使用约定等)。这取决于你的具体需求和项目结构。

另外,请注意代码中的注释和说明,它们提供了有关每个步骤和配置的额外信息。在实际项目中,你可能需要根据项目的实际情况和需求进行相应的调整和优化。例如,你可能需要处理循环依赖、配置作用域、使用拦截器或装饰器等高级功能。这些都可以在DryIoc和MediatR的文档中找到更详细的说明和示例。

3. MediatR2种传递方式

有了前面的基础知识准备,我们添加类库工程CodeWF.Tools.MediatR.Notifications,并添加请求定义(主工程及模块的响应处理程序需要实现):

public class TestRequest : IRequest<string>
{
public string? Args { get; set; }
}

添加通知定义:

public class TestNotification : INotification
{
public string? Args { get; set; }
}

请求和通知定义结构一样,只有一个字符串参数,只是实现接口不同。

4. 添加处理程序

示例工程结构如下,因为该开源项目(文末链接)写在站长的AvaloniaUI桌面工具工程,本文只关注如下图3个工程即可:

程序结构

在AvaloniaUI主工程(CodeWF.Tools.Desktop)添加请求响应处理程序:

public class TestHandler : IRequestHandler<TestRequest, string>
{
public async Task<string> Handle(TestRequest request, CancellationToken cancellationToken)
{
return await Task.FromResult($"主工程处理程序:Args = {request.Args}, Now = {DateTime.Now}");
}
}

添加通知响应处理程序:

public class TestNotificationHandler(INotificationService notificationService) : INotificationHandler<TestNotification>
{
public Task Handle(TestNotification notification, CancellationToken cancellationToken)
{
notificationService.Show("Notification",
$"主工程Notification处理程序:Args = {notification.Args}, Now = {DateTime.Now}");
return Task.CompletedTask;
}
}

在模块【CodeWF.Tools.Modules.SlugifyString】中添加请求响应处理程序(因为顺序关系,不会触发,这里添加只是演示请求为一对一响应):

public class TestHandler : IRequestHandler<TestRequest, string>
{
public async Task<string> Handle(TestRequest request, CancellationToken cancellationToken)
{
return await Task.FromResult($"模块【SlugifyString】Request处理程序:Args = {request.Args}, Now = {DateTime.Now}");
}
}

添加通知响应处理程序(会和主工程通知响应处理程序一样被触发):

public class TestNotificationHandler(INotificationService notificationService) : INotificationHandler<TestNotification>
{
public Task Handle(TestNotification notification, CancellationToken cancellationToken)
{
notificationService.Show("Notification",
$"模块【SlugifyString】Notification处理程序:Args = {notification.Args}, Now = {DateTime.Now}");
return Task.CompletedTask;
}
}

几个响应处理程序类定义类似:收到请求时,返回格式化字符串;收到通知时,弹出提示表明当前是哪个位置收到的通知,便于演示效果。

5. 请求和通知演示

触发操作我们写在模块【CodeWF.Tools.Modules.SlugifyString】中,在模块的ViewModel类里通过依赖注入获取请求和通知的发送者实例ISender和IPublisher:

using Unit = System.Reactive.Unit;

namespace CodeWF.Tools.Modules.SlugifyString.ViewModels;

public class SlugifyViewModel : ViewModelBase
{
// 省略别名转换相关逻辑代码,源码文末查看

private readonly INotificationService _notificationService;
private readonly IClipboardService? _clipboardService;
private readonly ITranslationService? _translationService;

public SlugifyViewModel(INotificationService notificationService, IClipboardService clipboardService,
ITranslationService translationService, ISender sender, IPublisher publisher
) : base(sender, publisher)

{
_notificationService = notificationService;
_clipboardService = clipboardService;
_translationService = translationService;
KindChanged = ReactiveCommand.Create<TranslationKind>(OnKindChanged);
}


public async Task ExecuteMediatRRequestAsync()
{
var result = Sender.Send(new TestRequest() { Args = To });
_notificationService.Show("MediatR", $"收到响应:{result.Result}");
}

public async Task ExecuteMediatRNotificationAsync()
{
await Publisher.Publish(new TestNotification() { Args = To });
}

}

点击测试MediatR-Request按钮触发调用ISender.Send发出请求并得到响应,通过点击测试MediatR-Notification按钮触发调用IPublisher.Publish发出通知。

请求效果:

看上面的请求效果:虽然在主工程和模块工程都注册了一个响应,但只有主工程被触发。

通知效果:

在主工程和模块工程都注册了一个通知响应,所以两个处理程序都弹出了提示。

6. 总结

为什么使用MediatR,而未使用Prism的事件聚合器?

站长开发工具做了在线版(https://blazor.dotnet9.com),也做了跨平台桌面版本(AvaloniaUI),两个版本使用MediatR可以复用大部分事件代码。

参考

文中写了主要代码,但可能缺失部分细节,源码链接如下,欢迎留言交流。

参考文章:MediatR 在 .NET 应用中的实践[1]

本文源码:Github[2]

参考资料
[1]

MediatR 在 .NET 应用中的实践: https://yimingzhi.net/2021/12/mediatr-zai-dotnet-ying-yong-zhong-de-shi-jian

[2]

Github: https://github.com/dotnet9/CodeWF/tree/main/src/CodeWF.Tools.Desktop


相关推荐

Python+ Appium:Android手机连接与操作详解(附源码)

在移动端自动化测试领域,Appium一直是最热门的开源工具之一。今天这篇文章,我们聚焦Android端自动化测试的完整流程,从环境配置到代码实战,一步一步带你掌握用Python控制Android...

全平台开源即时通讯IM框架MobileIMSDK开发指南,支持鸿蒙NEXT

写在前面在着手基于MobileIMSDK开发自已的即时通讯应用前,建议以Demo工程为脚手架,快速上手MobileIMSDK!Demo工程主要用于演示SDK的API调用等,它位于SDK完整下载包的如下...

移动开发(一):使用.NET MAUI开发第一个安卓APP

对于工作多年的C#程序员来说,近来想尝试开发一款安卓APP,考虑了很久最终选择使用.NETMAUI这个微软官方的框架来尝试体验开发安卓APP,毕竟是使用VisualStudio开发工具,使用起来也...

在安卓系统上开发一款软件详细的流程

安卓app软件开发流程是一个系统而复杂的过程,涉及多个阶段和环节。以下是一个典型的安卓软件开发流程概述:1.需求分析目的:了解用户需求,确定APP的目标、功能、特性和预期效果。活动:开发团队与客户进...

ArkUI-X在Android上使用Fragment开发指南

本文介绍将ArkUI框架的UIAbility跨平台部署至Android平台Fragment的使用说明,实现Android原生Fragment和ArkUI跨平台Fragment的混合开发,方便开发者灵活...

Web3开发者必须要知道的6个框架与开发工具

在Web3领域,随着去中心化应用和区块链的兴起,开发者们需要掌握适用于这一新兴技术的框架与开发工具。这些工具和框架能够提供简化开发流程、增强安全性以及提供更好的用户体验。1.Truffle:Truff...

Python开发web指南之创建你的RESTful APP

上回我们说到了:PythonFlask开发web指南:创建RESTAPI。我们知道了Flask是一个web轻量级框架,可以在上面做一些扩展,我们还用Flask创建了API,也说到了...

python的web开发框架有哪些(python主流web框架)

  python在web开发方面有着广泛的应用。鉴于各种各样的框架,对于开发者来说如何选择将成为一个问题。为此,我特此对比较常见的几种框架从性能、使用感受以及应用情况进行一个粗略的分析。  1Dja...

Qwik:革新Web开发的新框架(webview开源框架)

听说关注我的人,都实现了财富自由!你还在等什么?赶紧加入我们,一起走向人生巅峰!Qwik:革新Web开发的新框架Qwik橫空出世:一场颠覆前端格局的革命?是炒作还是未来?前端框架的更新迭代速度,如同...

Python中Web开发框架有哪些?(python主流web框架)

Python为Web开发提供了许多优秀的框架。以下是一些流行的PythonWeb框架:1.Django:一个高级的Web框架,旨在快速开发干净、实用的Web应用。Django遵...

WPF 工业自动化数据管控框架,支持热拔插 DLL与多语言实现

前言工业自动化开发中,设备数据的采集、处理与管理成为提升生产效率和实现智能制造的关键环节。为了简化开发流程、提高系统的灵活性与可维护性,StarRyEdgeFramework应运而生。该框架专注...

[汇川PLC] 汇川IFA程序框架06-建立气缸控制FB块

前言:汇川的iFA要跟西门子对标啦,这可是新的选择!就在2月14日,汇川刚发布的iFA平台,一眼就能看出来是对标西门子的全集成自动化平台博途(TIAPortal)。这个平台能在同一个...

微软发布.NET 10首个预览版:JIT编译器再进化、跨平台开发更流畅

IT之家2月26日消息,微软.NET团队昨日(2月25日)发布博文,宣布推出.NET10首个预览版更新,重点改进.NETRuntime、SDK、libraries、C#、AS...

大模型部署革命:GGUF量化+vLLM推理的极致性能调优方案

本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在官网-聚客AI学院大模型应用开发微调项目实践课程学习平台一、模型微调核心概念与技术演进1.1微调的本质与优势数学表达:1....

拓扑学到底在研究什么?(拓扑学到底在研究什么问题)

拓扑是“不量尺寸的几何学”,那么它的核心内容,主要方法是什么?如果你问罗巴切夫斯基,他会说“附贴性是物体的一个特殊的属性。如果我们把这个性质掌握,而把物体其他的一切属性,不问是本质的或偶然出现的,均不...

取消回复欢迎 发表评论: