こんにちは、私は砂漠の果ての狼です!
AvaloniaUIは、開発者がWindows、Linux、macOS、Android、iOSを含む複数のプラットフォーム向けのアプリケーションを構築できるようにする強力なクロスプラットフォームの. NETクライアント開発フレームワークです。複雑なアプリケーションを構築する場合、モジュール性とコンポーネント間の通信が特に重要になります。Prismフレームワークはモジュール化された開発方式を提供し、プラグインのホットプラグをサポートしているが、MediatRは仲介者Mediatorモードを実装したイベントサブスクライブパブリッシングフレームワークであり、モジュール間およびモジュールとメインプログラム間の通信に最適である。
本文重点是介绍MediatR,它 是 .NET 中的开源简单中介者模式实现。它通过一种进程内消息传递机制(无其他外部依赖),进行请求/响应、命令、查询、通知和事件的消息传递,并通过泛型来支持消息的智能调度。开源库地址是 https://github.com/jbogard/MediatR。
この記事では、AvaloniaプロジェクトでMediatRとMicrosoftのDependency Injection(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不返回值。
リクエスト型ごとに、対応するハンドラーインターフェイスがあります。
IRequestHandler<T, U>实现该接口并返回Task<U>RequestHandler<T, U>继承该类并返回UIRequestHandler<T>实现该接口并返回Task<Unit>AsyncRequestHandler<T>继承该类并返回TaskRequestHandler<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;
}
}
在上面的代码中,我们重写了CreateContainerRules、CreateContainerExtension和RegisterRequiredTypes方法以配置DryIoc容器,并注册了MediatR服务和相关处理程序。
MediatRサービスを登録する際には、現在ロードされているアセンブリのリストからハンドラを検索して登録します。モジュールがオンデマンドでロードされる場合は、ハンドラを登録する前に、適切なモジュールがロードされていることを確認してください。
さらに、ハンドラーを登録するためにモジュールアセンブリを手動でリストに追加する方法を示しました。これは、登録されるモジュールやハンドラを明示的に制御する必要がある場合に便利です。ただし、ほとんどの場合、モジュールやハンドラのロードと登録には、より自動化された方法を使用したい場合があります(特定のディレクトリをスキャンしたり、規約を使用したりするなど)。これは、特定のニーズとプロジェクト構造に依存します。
また、各ステップと設定に関する追加情報を提供するコード内のコメントと説明にも注意してください。実際のプロジェクトでは、プロジェクトの実際の状況やニーズに合わせて調整や最適化を行う必要がある場合があります。たとえば、循環依存関係の処理、スコープの設定、インターセプタやデコレータの使用などの高度な機能が必要な場合があります。これらの詳細な説明と例は、DryIocとMediatRのドキュメントにあります。
3. MediatRの2つの配信方法
有了前面的基础知识准备,我们添加类库工程CodeWF.Tools.MediatR.Notifications,并添加请求定义(主工程及模块的响应处理程序需要实现):
public class TestRequest : IRequest<string>
{
public string? Args { get; set; }
}
通知定義の追加:
public class TestNotification : INotification
{
public string? Args { get; set; }
}
リクエストと通知の定義構造は同じで(実装インタフェースが異なる)、文字列プロパティは1つだけです。
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]にリクエスト応答ハンドラを追加します(順序のため、トリガーされません。ここで追加するのはリクエストが1対1の応答であることを示すだけです)。
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. プレゼンテーションのリクエストと通知
モジュール[Code WF. 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. まとめまとめまとめ
PrismのイベントアグリゲーターではなくMediatRを使う理由は?
ウェブマスター開発ツールはオンライン版(https//www.example.com)とクロスプラットフォームデスクトップ版(AvaloniaUI)があり、どちらのバージョンでもMediatRを使用してイベントコードの大部分を再利用できます。
CQRS or DDD?
这节直接复制MediatR 在 .NET 应用中的实践 - 明志唯新 (yimingzhi.net),大家应该可以学到些什么:
软件开发发展到今天,模式和理念不断在架构中刷新:从分布式到微服务,再到云原生 ……。时代对一个程序员,尤其是服务端程序员,提出的要求越来越高。DDD(领域驱动设计)在微服务架构中一再被提及,甚至有人提出这是必须项!
实施一个完美的 DDD 还是有难度的,现实中奋战在一线的 CRUD 程序员还是不少。那么在 CRUD 和 DDD 之间我们是否还有缓冲区呢?MediatR 的作者曾经也撰文讨论过这个问题,我很认同他的基本观点:设计是为应用服务的,不能为了 DDD 而 DDD。
CQRS 的全称是:"Command and Query Responsibility Segregation",直译过来就是命令与查询责任分离,可以通俗的理解为 读写分离。

マイクロソフトの公式文書では以下のように述べられている。
CQRSコマンドとクエリの責任は、データストアの読み取りと更新操作を分離したパターンです。アプリケーションにCQRSを実装すると、パフォーマンス、スケーラビリティ、セキュリティが最大化されます。CQRSへの移行によって作成された柔軟性により、システムは時間の経過とともに進化し、更新コマンドがドメインレベルでマージ競合を引き起こすことを防ぎます。
マイクロソフトは適切な分離モデルソリューションも提供している。
CQRSはコマンドを使用してデータを更新し、クエリを使用してデータを読み取り、読み取りと書き込みを異なるモデルに分離します。
- コマンドは、データ中心ではなく、タスクベースである必要があります。
- コマンドは同期処理ではなく非同期処理のためにキューに入れられます。
- クエリーはデータベースを変更しません。クエリによって返されるDTOはドメイン知識をカプセル化しません。

CQRSの利点は以下のとおり。
- 独立スケーリング ** EqualLogic CQRSでは、読み取りおよび書き込みワークロードを独立にスケーリングできます。これにより、ロック競合が軽減される可能性があります。
- 最適化されたデータ·スキーマ ** 読み取り側ではクエリに最適化されたスキーマを使用でき、書き込み側では更新に最適化されたスキーマを使用できます。
- セキュリティ **:適切なドメイン·エンティティのみがデータに対する書き込みを実行することを容易にします。
- 関心の分離 ** 読み取り側と書き込み側を分離することで、モデルの保守性が向上し、柔軟性が向上します。ほとんどの複雑なビジネスロジックは、書き込みモデルに分割されます。読み取りモデルは比較的簡単です。
- クエリはより単純に ** 実体化ビュー Materialized Viewを読み取りデータベースに格納することで、アプリケーションはクエリ時に複雑な結合を回避できます。
有了 MediatR 我们可以在应用中轻松实现 CQRS:
IRequest<>的消息名称以Command为结尾的是命令,其对应的 Handler 执行写任务IRequest<>的消息名称以Query为结尾的是查询,其对应的 Handler 执行读数据
結びの言葉
MediatR 是一个简单的中介者实现,可以极大降低我们的应用复杂度,也能够使得我们一路从 CRUD 到 CQRS 到 DDD 进行逐级演进。毕竟我们是生活在现实中的人,不能罔顾商业现实,纯粹一味追求技术。

ビジネステクノロジーの進化は、革命ではなく継続的な改革であるべきです。流行は常に繰り返されますが、私たちは比較的簡単に生きなければなりません。
リファレンス
テキストの例はメインコードを書いたが、詳細の一部が欠けている可能性があり、ソースリンクは次のとおりです。
本文源码:GitHub