Hello everyone, I am a wolf at the end of the desert!
AvaloniaUI is a powerful cross-platform. NET client development framework that allows developers to build applications for multiple platforms such as Windows, Linux, macOS, Android and iOS. When building complex applications, modularity and communication between components become particularly important. The Prism framework provides a modular development method and supports hot plugging of plug-ins, while MediatR is an event subscription publishing framework that implements the Mediator pattern, which is very suitable for communication between modules and between modules and main programs.
本文重点是介绍MediatR,它 是 .NET 中的开源简单中介者模式实现。它通过一种进程内消息传递机制(无其他外部依赖),进行请求/响应、命令、查询、通知和事件的消息传递,并通过泛型来支持消息的智能调度。开源库地址是 https://github.com/jbogard/MediatR。
This article details how to use MediatR and Microsoft's Dependency Injection (MS.DI) libraries to achieve event-driven communication in the Avalonia project.

0. Basic knowledge preparation-basic usage of MediatR
MediatR中有两种消息传递的方式:
Request/Response,用于一个单独的Handler。Notification,用于多个Handler。
Request/Response
Request/Response 有点类似于 HTTP 的 Request/Response,发出一个 Request 会得到一个 Response。
Request 消息在 MediatR 中,有两种类型:
IRequest<T>返回一个T类型的值。IRequest不返回值。
For each request type, there is a corresponding handler interface:
IRequestHandler<T, U>实现该接口并返回Task<U>RequestHandler<T, U>继承该类并返回UIRequestHandler<T>实现该接口并返回Task<Unit>AsyncRequestHandler<T>继承该类并返回TaskRequestHandler<T>继承该类不返回
Notification
Notification 就是通知,调用者发出一次,然后可以有多个处理者参与处理。

1. preparations
首先,确保你的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. Configure containers and register services
在Avalonia项目中,你需要配置DryIoc容器以使用Microsoft的DI扩展,并注册MediatR服务。这通常在你的主启动类(如App.axaml.cs)中完成。
The following is sample code for configuring containers and registering services:
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;
}
}
在上面的代码中,我们重写了CreateContainerRules、CreateContainerExtension和RegisterRequiredTypes方法以配置DryIoc容器,并注册了MediatR服务和相关处理程序。
Note that when registering the MediatR service, we find and register handlers from the list of currently loaded assemblies. If modules are loaded on demand, make sure that the appropriate modules are loaded before registering the handler.
In addition, we demonstrated how to manually add module assemblies to the list to register handlers. This is often useful when you need to explicitly control which modules and handlers are registered. However, please note that in most cases, you may want to use a more automated way to load and register modules and handlers (for example, by scanning specific directories or using conventions, etc.). This depends on your specific needs and project structure.
Also, pay attention to the comments and instructions in the code, which provide additional information about each step and configuration. In actual projects, you may need to make corresponding adjustments and optimizations based on the actual situation and needs of the project. For example, you may need advanced features such as handling circular dependencies, configuring scopes, and using interceptors or decorators. More detailed descriptions and examples of these can be found in DryIoc and MediatR's documentation.
3. MediatR2 delivery methods
有了前面的基础知识准备,我们添加类库工程CodeWF.Tools.MediatR.Notifications,并添加请求定义(主工程及模块的响应处理程序需要实现):
public class TestRequest : IRequest<string>
{
public string? Args { get; set; }
}
Add notification definitions:
public class TestNotification : INotification
{
public string? Args { get; set; }
}
The request and notification definition structure is the same (implementation interfaces are different), with only one string attribute.
4. add handlers
The example project structure is as follows. Because this open source project (link at the end of the article) is written in the AvaloniaUI Desktop Tools Project of the webmaster, this article only focuses on the three projects shown in the following figure:

Add a request response handler to the AvaloniaUI main project (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}");
}
}
Add notification response handlers:
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;
}
}
Add a request response handler to the module [CodeWF.Tools.Modules.SlugifyString](because of the order, it will not be triggered, and the addition here is just to demonstrate that the request is a one-to-one response):
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}");
}
}
Add a notification response handler (will be triggered like the main project notification response handler):
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;
}
}
Several response handler classes have similar definitions: when a request is received, a formatted string is returned; when a notification is received, a pop-up prompt indicates where the notification is currently received, for ease of demonstration.
5. Request and Notification Presentation
We write the trigger operation in the module [CodeWF.Tools.Modules.SlugifyString], and obtain the sender instances ISender and IPublicer of the request and notification through dependency injection in the module's ViewModel class:
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发出通知。
Request effect:

Look at the effect of the request above: Although a response is registered in both the main project and the module project, only the main project is triggered.
Notification effect:

A notification response is registered in both the main project and the module project, so prompts pop up for both handlers.
6. summary
Why use MediatR instead of Prism's event aggregator?
The webmaster development tool is available in an online version (blazor.dotnet9.com) and a cross-platform desktop version (AvaloniaUI). Both versions can reuse most event code using MediatR.
CQRS or DDD?
这节直接复制MediatR 在 .NET 应用中的实践 - 明志唯新 (yimingzhi.net),大家应该可以学到些什么:
软件开发发展到今天,模式和理念不断在架构中刷新:从分布式到微服务,再到云原生 ……。时代对一个程序员,尤其是服务端程序员,提出的要求越来越高。DDD(领域驱动设计)在微服务架构中一再被提及,甚至有人提出这是必须项!
实施一个完美的 DDD 还是有难度的,现实中奋战在一线的 CRUD 程序员还是不少。那么在 CRUD 和 DDD 之间我们是否还有缓冲区呢?MediatR 的作者曾经也撰文讨论过这个问题,我很认同他的基本观点:设计是为应用服务的,不能为了 DDD 而 DDD。
CQRS 的全称是:"Command and Query Responsibility Segregation",直译过来就是命令与查询责任分离,可以通俗的理解为 读写分离。

Microsoft's official documents have made the following statement:
CQRS commands and query responsibilities are separated. Data storage is a pattern of separating read and update operations. Implementing CQRS in an application can maximize its performance, scalability, and security. The flexibility created by migrating to CQRS allows the system to evolve better over time and prevents update commands from causing merge conflicts at the domain level.
Microsoft also provided corresponding isolation model solutions:
CQRS uses commands to update data, queries to read data, and separates reads and writes into different models.
- Commands should be task-based rather than data-centric.
- Commands can be placed in queues for asynchronous processing rather than synchronous processing.
- A query never modifies the database. The DTO returned by the query does not encapsulate any domain knowledge.

Benefits of CQRS include:
- ** Independent scaling **: CQRS allows read and write workloads to be scaled independently, which may reduce lock contention.
- ** Optimized data architecture **: The reader can use an architecture optimized for queries, and the writer can use an architecture optimized for updates.
- ** Security **: Make it easier to ensure that only the correct domain entities write to data.
- ** Separation of concerns **: Separating the read and write sides makes the model easier to maintain and more flexible. Most complex business logic is assigned to write models. Reading models will become relatively simple.
- ** Query is simpler **: By storing the materialized views in the read database, applications can avoid complex joins when querying.
有了 MediatR 我们可以在应用中轻松实现 CQRS:
IRequest<>的消息名称以Command为结尾的是命令,其对应的 Handler 执行写任务IRequest<>的消息名称以Query为结尾的是查询,其对应的 Handler 执行读数据
concluding remarks
MediatR 是一个简单的中介者实现,可以极大降低我们的应用复杂度,也能够使得我们一路从 CRUD 到 CQRS 到 DDD 进行逐级演进。毕竟我们是生活在现实中的人,不能罔顾商业现实,纯粹一味追求技术。

The evolution of business technology should be a continuous reform rather than a revolution. There will always be recurring epidemics, but we have to live, live relatively easily!
reference
The main code is written in the examples in this article, but some details may be missing. The source code link is as follows. Welcome to leave a message and exchange.
本文源码:GitHub