在使用领域驱动设计(DDD)重构的 `WpfPowerTester` 项目中,引入 **MediatR** 库可以进一步优化事件驱动架构,替代现有的静态 `DomainEventPublisher`
MediatR是一个轻量级的 .NET 库,用于实现中介者模式(Mediator Pattern),帮助解耦应用程序中的请求处理、命令、查询和事件。命令(Command):通过发送命令,触发业务逻辑并返回结果。查询(Query):通过执行查询,返回数据。通知(Notification):通过发布领域事件,允许多个处理程序订阅。管道行为(Pipeline Behaviors):支持横切关注点(如日志
在使用领域驱动设计(DDD)重构的 WpfPowerTester
项目中,引入 MediatR 库可以进一步优化事件驱动架构,替代现有的静态 DomainEventPublisher
,并提供更强大的请求/响应模式来处理命令和查询。以下是对 MediatR 的详细讲解,结合 IRequest<Guid>
的用法,融入到 WpfPowerTester
项目中,并提供中文注释和代码示例。
一、MediatR 概述
MediatR 是一个轻量级的 .NET 库,用于实现中介者模式(Mediator Pattern),帮助解耦应用程序中的请求处理、命令、查询和事件。它广泛用于 DDD 和 CQRS(命令查询职责分离)架构中,主要功能包括:
- 命令(Command):通过
IRequest<TResponse>
发送命令,触发业务逻辑并返回结果。 - 查询(Query):通过
IRequest<TResponse>
执行查询,返回数据。 - 通知(Notification):通过
INotification
发布领域事件,允许多个处理程序订阅。 - 管道行为(Pipeline Behaviors):支持横切关注点(如日志、验证、事务)。
在 WpfPowerTester
项目中,MediatR 可以用来:
- 替换静态的
DomainEventPublisher
,实现更灵活的领域事件发布。 - 处理命令(如启动、暂停项目)和查询(如获取项目列表)。
- 解耦
TestViewModel
和ProjectApplicationService
。
二、IRequest 详解
IRequest<TResponse>
是 MediatR 中的核心接口,用于定义一个请求及其返回类型。IRequest<Guid>
表示一个请求的响应类型为 Guid
,通常用于返回唯一标识符(如项目 ID、设备 ID)。以下是其关键点:
-
定义:
public interface IRequest<out TResponse> { }
TResponse
是请求的返回类型,IRequest<Guid>
表示返回一个Guid
类型的结果。- 请求类(如命令或查询)实现此接口,包含请求所需的参数。
-
典型用途:
- 命令:如
StartProjectCommand
,返回新创建或操作的项目的Guid
。 - 查询:如
GetProjectByIdQuery
,返回指定项目的Guid
。 - 用于需要返回唯一标识符的场景,例如创建新项目后返回其 ID。
- 命令:如
-
与 MediatR 的交互:
- 定义一个请求类(如
StartProjectCommand
),实现IRequest<Guid>
。 - 定义一个处理程序(
IRequestHandler<StartProjectCommand, Guid>
),实现业务逻辑。 - 使用
IMediator
发送请求并获取响应。
- 定义一个请求类(如
三、在 WpfPowerTester 中集成 MediatR
以下是将 MediatR 和 IRequest<Guid>
集成到 WpfPowerTester
项目中的步骤,结合 DDD 架构。
3.1 安装 MediatR
在项目中添加以下 NuGet 包:
MediatR
:核心库。MediatR.Extensions.Microsoft.DependencyInjection
:用于依赖注入。
在 WpfPowerTester.UI
项目中执行:
dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
3.2 配置 MediatR
在 App.xaml.cs
中注册 MediatR:
// WpfPowerTester.UI/App.xaml.cs
using Microsoft.Extensions.DependencyInjection;
using Prism.Ioc;
using MediatR;
using WpfPowerTester.Application.Services;
using WpfPowerTester.Infrastructure.Repositories;
namespace WpfPowerTester.UI
{
public partial class App
{
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// 注册仓储和服务
containerRegistry.RegisterSingleton<IProjectRepository, ProjectRepository>();
containerRegistry.RegisterSingleton<IZoneRepository, ZoneRepository>();
containerRegistry.RegisterSingleton<ProjectApplicationService>();
// 注册 MediatR
containerRegistry.RegisterServices(services =>
{
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(App).Assembly));
});
// 注册视图
containerRegistry.RegisterForNavigation<MainView>();
containerRegistry.RegisterForNavigation<TestView>();
containerRegistry.RegisterForNavigation<ZoneView>();
containerRegistry.RegisterDialog<SwitchProjectView>();
}
protected override Window CreateShell()
{
return Container.Resolve<MainView>();
}
}
}
说明:
AddMediatR
自动扫描程序集,注册所有IRequestHandler
和INotificationHandler
。- 确保 MediatR 扫描包含命令、查询和事件的程序集。
3.3 定义命令和处理程序
以启动项目为例,定义 StartProjectCommand
和对应的处理程序。
// WpfPowerTester.Application/Commands/StartProjectCommand.cs
using MediatR;
using System;
namespace WpfPowerTester.Application.Commands
{
public class StartProjectCommand : IRequest<Guid>
{
public Guid ProjectId { get; }
public StartProjectCommand(Guid projectId)
{
ProjectId = projectId;
}
}
}
// WpfPowerTester.Application/Handlers/StartProjectCommandHandler.cs
using MediatR;
using System;
using System.Threading;
using System.Threading.Tasks;
using WpfPowerTester.Domain.Aggregates;
using WpfPowerTester.Infrastructure.Repositories;
namespace WpfPowerTester.Application.Handlers
{
public class StartProjectCommandHandler : IRequestHandler<StartProjectCommand, Guid>
{
private readonly IProjectRepository _projectRepository;
public StartProjectCommandHandler(IProjectRepository projectRepository)
{
_projectRepository = projectRepository;
}
public async Task<Guid> Handle(StartProjectCommand request, CancellationToken cancellationToken)
{
var project = await _projectRepository.GetByIdAsync(request.ProjectId);
if (project == null)
{
throw new InvalidOperationException("项目不存在");
}
project.Start();
await _projectRepository.SaveAsync(project);
return project.Id;
}
}
}
说明:
StartProjectCommand
实现IRequest<Guid>
,表示返回项目的Guid
。StartProjectCommandHandler
实现IRequestHandler<StartProjectCommand, Guid>
,调用领域模型的Start
方法。- 异常处理和持久化逻辑在处理程序中完成。
3.4 重构领域事件为 MediatR 通知
将现有的 DomainEventPublisher
替换为 MediatR 的 INotification
。
// WpfPowerTester.Domain/DomainEvents/ProjectStartedEvent.cs
using MediatR;
namespace WpfPowerTester.Domain.DomainEvents
{
public class ProjectStartedEvent : INotification
{
public Guid ProjectId { get; }
public string ProjectName { get; }
public ProjectStartedEvent(Guid projectId, string projectName)
{
ProjectId = projectId;
ProjectName = projectName;
}
}
}
// WpfPowerTester.Application/Handlers/ProjectStartedEventHandler.cs
using MediatR;
using System.Threading;
using System.Threading.Tasks;
using WpfPowerTester.Common;
namespace WpfPowerTester.Application.Handlers
{
public class ProjectStartedEventHandler : INotificationHandler<ProjectStartedEvent>
{
private readonly IEventAggregator _eventAggregator;
public ProjectStartedEventHandler(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
public Task Handle(ProjectStartedEvent notification, CancellationToken cancellationToken)
{
// 发布 Prism 事件,通知 UI
_eventAggregator.GetEvent<MainViewMessageEvent>().Publish($"工程 {notification.ProjectName} 已启动");
return Task.CompletedTask;
}
}
}
说明:
ProjectStartedEvent
实现INotification
,无需返回值。ProjectStartedEventHandler
订阅事件,通知 UI 更新消息。- 多个处理程序可以订阅同一个事件,实现解耦。
3.5 更新 ProjectApplicationService
调整 ProjectApplicationService
使用 MediatR 发送命令。
// WpfPowerTester.Application/Services/ProjectApplicationService.cs
using MediatR;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using WpfPowerTester.Application.Commands;
using WpfPowerTester.Domain.Aggregates;
using WpfPowerTester.Infrastructure.Repositories;
namespace WpfPowerTester.Application.Services
{
public class ProjectApplicationService
{
private readonly IMediator _mediator;
private readonly IProjectRepository _projectRepository;
public ProjectApplicationService(IMediator mediator, IProjectRepository projectRepository)
{
_mediator = mediator;
_projectRepository = projectRepository;
}
public async Task<List<Project>> GetAllProjectsAsync()
{
return await _projectRepository.GetAllAsync();
}
public async Task<Project> GetProjectByIdAsync(Guid projectId)
{
return await _projectRepository.GetByIdAsync(projectId);
}
public async Task<Guid> StartProjectAsync(Guid projectId)
{
return await _mediator.Send(new StartProjectCommand(projectId));
}
public async Task<Guid> PauseProjectAsync(Guid projectId)
{
return await _mediator.Send(new PauseProjectCommand(projectId));
}
public async Task<Guid> ResumeProjectAsync(Guid projectId)
{
return await _mediator.Send(new ResumeProjectCommand(projectId));
}
public async Task<Guid> StopProjectAsync(Guid projectId)
{
return await _mediator.Send(new StopProjectCommand(projectId));
}
public async Task<Guid> SetProjectZoneAsync(Guid projectId, string zone)
{
return await _mediator.Send(new SetProjectZoneCommand(projectId, zone));
}
}
}
说明:
ProjectApplicationService
注入IMediator
,使用命令(如StartProjectCommand
)处理业务逻辑。- 每个方法返回
Guid
,符合IRequest<Guid>
的定义。
3.6 更新 TestViewModel
调整 TestViewModel
使用 MediatR 调用命令。
// WpfPowerTester.UI/ViewModels/TestViewModel.cs
using MediatR;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using Prism.Services.Dialogs;
using System;
using System.Windows;
using System.Windows.Controls;
using WpfPowerTester.Application.Commands;
using WpfPowerTester.Application.Services;
using WpfPowerTester.Common;
using WpfPowerTester.Domain.Aggregates;
using WpfPowerTester.Domain.DomainEvents;
namespace WpfPowerTester.UI.ViewModels
{
[RegionMemberLifetime(KeepAlive = true)]
public class TestViewModel : BindableBase, INavigationAware
{
private readonly IMediator _mediator;
private readonly ProjectApplicationService _projectService;
private readonly IRegionManager _regionManager;
private readonly IDialogService _dialogService;
private Project _selectedProject;
private Visibility _isPCResultShow = Visibility.Hidden;
private int _testProjectIndex;
public Project SelectedProject
{
get => _selectedProject;
set
{
if (_selectedProject == value) return;
if (_selectedProject != null)
{
_selectedProject.IsProjectHide = false;
_selectedProject.ChartValueList.Clear();
_selectedProject.SelectedChartStationList.Clear();
Global.EventAggregator.GetEvent<PCisLock>().Publish(false);
}
_selectedProject = value;
if (_selectedProject != null)
{
IsPCResultShow = _selectedProject.Parameters.ProjectMethod == "PC" ||
_selectedProject.Parameters.ProjectMethod == "热阻"
? Visibility.Visible : Visibility.Hidden;
_selectedProject.IsFitLockShow = (_selectedProject.Parameters.ProjectMethod == "PC" && !_selectedProject.IsFitLockFrozen)
? Visibility.Visible : Visibility.Collapsed;
}
RaisePropertyChanged();
}
}
public Visibility IsPCResultShow
{
get => _isPCResultShow;
set => SetProperty(ref _isPCResultShow, value);
}
public int TestProjectIndex
{
get => _testProjectIndex;
set => SetProperty(ref _testProjectIndex, value);
}
public DelegateCommand<object> RunCommand { get; private set; }
// 其他命令保持不变...
public TestViewModel(IMediator mediator, ProjectApplicationService projectService, IRegionManager regionManager, IDialogService dialogService)
{
_mediator = mediator;
_projectService = projectService;
_regionManager = regionManager;
_dialogService = dialogService;
RunCommand = new DelegateCommand<object>(Run);
// 初始化其他命令...
// 订阅 Prism 事件
Global.EventAggregator.GetEvent<SelectTestProjectIndexEvent>().Subscribe(index => TestProjectIndex = index);
}
private async void Run(object obj)
{
if (SelectedProject == null) return;
await _mediator.Send(new StartProjectCommand(SelectedProject.Id));
}
// 其他命令方法类似,使用 _mediator.Send 调用命令
}
}
说明:
TestViewModel
注入IMediator
,通过Send
方法调用命令。- 命令(如
RunCommand
)直接发送StartProjectCommand
,无需直接调用ProjectApplicationService
。
3.7 替换 DomainEventPublisher
移除静态的 DomainEventPublisher
,在 Project
中使用 IMediator
发布事件。
// WpfPowerTester.Domain/Aggregates/Project.cs
using MediatR;
using System;
using System.Collections.Generic;
using WpfPowerTester.Domain.DomainEvents;
using WpfPowerTester.Domain.Entities;
using WpfPowerTester.Domain.ValueObjects;
namespace WpfPowerTester.Domain.Aggregates
{
public class Project
{
private readonly IMediator _mediator;
public Guid Id { get; private set; }
public string ProjectName { get; private set; }
public DateTime ProjectDate { get; private set; }
public string ProjectNum { get; private set; }
public TestParameters Parameters { get; private set; }
public int ProjectRunStatus { get; private set; }
public string ProjectSelectedZone { get; private set; }
public bool ProjectSelectedZoneIsEnable { get; private set; }
public bool IsProjectHide { get; private set; }
public List<TestResult> ResultList { get; private set; } = new List<TestResult>();
public List<PCResult> PCResultList { get; private set; } = new List<PCResult>();
public List<ChartValue> ChartValueList { get; private set; } = new List<ChartValue>();
public List<string> SelectedChartStationList { get; private set; } = new List<string>();
public Project(string projectName, string projectNum, TestParameters parameters, IMediator mediator)
{
Id = Guid.NewGuid();
ProjectName = projectName ?? throw new ArgumentNullException(nameof(projectName));
ProjectNum = projectNum ?? throw new ArgumentNullException(nameof(projectNum));
Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
_mediator = mediator;
ProjectDate = DateTime.Now;
ProjectRunStatus = 0;
ProjectSelectedZoneIsEnable = true;
IsProjectHide = false;
}
public void Start()
{
if (ProjectRunStatus == 1) throw new InvalidOperationException("工程已在运行");
ProjectRunStatus = 1;
_mediator.Publish(new ProjectStartedEvent(Id, ProjectName)).GetAwaiter().GetResult();
}
// 其他方法类似,使用 _mediator.Publish 发布事件
}
}
说明:
Project
构造函数注入IMediator
,用于发布领域事件。- 使用
_mediator.Publish
替代DomainEventPublisher.Publish
。 - 注意:同步调用
GetAwaiter().GetResult()
是为了简化示例,实际项目中应使用异步方法。
四、IRequest 的其他应用场景
-
创建新项目:
public class CreateProjectCommand : IRequest<Guid> { public string ProjectName { get; } public string ProjectNum { get; } public TestParameters Parameters { get; } public CreateProjectCommand(string projectName, string projectNum, TestParameters parameters) { ProjectName = projectName; ProjectNum = projectNum; Parameters = parameters; } } public class CreateProjectCommandHandler : IRequestHandler<CreateProjectCommand, Guid> { private readonly IProjectRepository _projectRepository; private readonly IMediator _mediator; public CreateProjectCommandHandler(IProjectRepository projectRepository, IMediator mediator) { _projectRepository = projectRepository; _mediator = mediator; } public async Task<Guid> Handle(CreateProjectCommand request, CancellationToken cancellationToken) { var project = new Project(request.ProjectName, request.ProjectNum, request.Parameters, _mediator); await _projectRepository.SaveAsync(project); return project.Id; } }
-
查询项目:
public class GetProjectByIdQuery : IRequest<Guid> { public Guid ProjectId { get; } public GetProjectByIdQuery(Guid projectId) { ProjectId = projectId; } } public class GetProjectByIdQueryHandler : IRequestHandler<GetProjectByIdQuery, Guid> { private readonly IProjectRepository _projectRepository; public GetProjectByIdQueryHandler(IProjectRepository projectRepository) { _projectRepository = projectRepository; } public async Task<Guid> Handle(GetProjectByIdQuery request, CancellationToken cancellationToken) { var project = await _projectRepository.GetByIdAsync(request.ProjectId); return project?.Id ?? Guid.Empty; } }
五、MediatR 的优势与注意事项
5.1 优势
-
解耦:命令、查询和事件处理程序完全解耦,
TestViewModel
不直接依赖ProjectApplicationService
。 -
可扩展:支持多个处理程序处理同一事件,适合复杂业务逻辑。
-
管道行为:可以添加验证、日志、事务等横切逻辑。例如:
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> { public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) { // 日志记录 Console.WriteLine($"Handling {typeof(TRequest).Name}"); var response = await next(); Console.WriteLine($"Handled {typeof(TRequest).Name}"); return response; } }
在
App.xaml.cs
中注册:services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(App).Assembly); cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>)); });
-
测试性:命令和处理程序易于单元测试。
5.2 注意事项
- 性能:MediatR 使用反射解析处理程序,需注意性能开销,尽量使用单例注册。
- 异步优先:命令和事件处理应尽量使用异步方法,避免阻塞 UI。
- 命名规范:命令以
Command
结尾,查询以Query
结尾,事件以Event
结尾,保持代码可读性。 - 错误处理:在处理程序中添加适当的异常处理,结合 UI 层显示错误信息。
六、在 WpfPowerTester 中的运行流程
-
启动项目:
- 用户在
TestView
点击“启动”按钮,触发RunCommand
。 TestViewModel
发送StartProjectCommand
:await _mediator.Send(new StartProjectCommand(SelectedProject.Id));
StartProjectCommandHandler
调用Project.Start()
,触发ProjectStartedEvent
。ProjectStartedEventHandler
更新 UI 消息。
- 用户在
-
事件通知:
ProjectStartedEvent
由 MediatR 发布,多个处理程序(如日志、UI 更新)处理事件。
-
图表更新:
- 图表相关命令(如
ChartShowCommand
)可定义为IRequest<Guid>
,返回更新后的图表 ID。
- 图表相关命令(如
七、总结
通过将 MediatR 和 IRequest<Guid>
集成到 WpfPowerTester
项目中,实现了以下改进:
- 命令和查询:使用
StartProjectCommand
等命令,明确请求和响应类型。 - 事件解耦:用
INotification
替换静态DomainEventPublisher
,支持多处理程序。 - 架构清晰:应用层通过 MediatR 调用领域逻辑,保持 DDD 分层。
- 可扩展性:新增命令、查询和事件只需定义新类,无需修改现有代码。
建议进一步实现图表服务(ChartService
)和设备模型(Device
),并添加管道行为(如日志、验证)以增强功能。
更多推荐
所有评论(0)