在使用领域驱动设计(DDD)重构的 WpfPowerTester 项目中,引入 MediatR 库可以进一步优化事件驱动架构,替代现有的静态 DomainEventPublisher,并提供更强大的请求/响应模式来处理命令和查询。以下是对 MediatR 的详细讲解,结合 IRequest<Guid> 的用法,融入到 WpfPowerTester 项目中,并提供中文注释和代码示例。


一、MediatR 概述

MediatR 是一个轻量级的 .NET 库,用于实现中介者模式(Mediator Pattern),帮助解耦应用程序中的请求处理、命令、查询和事件。它广泛用于 DDD 和 CQRS(命令查询职责分离)架构中,主要功能包括:

  1. 命令(Command):通过 IRequest<TResponse> 发送命令,触发业务逻辑并返回结果。
  2. 查询(Query):通过 IRequest<TResponse> 执行查询,返回数据。
  3. 通知(Notification):通过 INotification 发布领域事件,允许多个处理程序订阅。
  4. 管道行为(Pipeline Behaviors):支持横切关注点(如日志、验证、事务)。

WpfPowerTester 项目中,MediatR 可以用来:

  • 替换静态的 DomainEventPublisher,实现更灵活的领域事件发布。
  • 处理命令(如启动、暂停项目)和查询(如获取项目列表)。
  • 解耦 TestViewModelProjectApplicationService

二、IRequest 详解

IRequest<TResponse> 是 MediatR 中的核心接口,用于定义一个请求及其返回类型。IRequest<Guid> 表示一个请求的响应类型为 Guid,通常用于返回唯一标识符(如项目 ID、设备 ID)。以下是其关键点:

  1. 定义

    public interface IRequest<out TResponse>
    {
    }
    
    • TResponse 是请求的返回类型,IRequest<Guid> 表示返回一个 Guid 类型的结果。
    • 请求类(如命令或查询)实现此接口,包含请求所需的参数。
  2. 典型用途

    • 命令:如 StartProjectCommand,返回新创建或操作的项目的 Guid
    • 查询:如 GetProjectByIdQuery,返回指定项目的 Guid
    • 用于需要返回唯一标识符的场景,例如创建新项目后返回其 ID。
  3. 与 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 自动扫描程序集,注册所有 IRequestHandlerINotificationHandler
  • 确保 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 的其他应用场景

  1. 创建新项目

    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;
        }
    }
    
  2. 查询项目

    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 中的运行流程

  1. 启动项目

    • 用户在 TestView 点击“启动”按钮,触发 RunCommand
    • TestViewModel 发送 StartProjectCommand
      await _mediator.Send(new StartProjectCommand(SelectedProject.Id));
      
    • StartProjectCommandHandler 调用 Project.Start(),触发 ProjectStartedEvent
    • ProjectStartedEventHandler 更新 UI 消息。
  2. 事件通知

    • ProjectStartedEvent 由 MediatR 发布,多个处理程序(如日志、UI 更新)处理事件。
  3. 图表更新

    • 图表相关命令(如 ChartShowCommand)可定义为 IRequest<Guid>,返回更新后的图表 ID。

七、总结

通过将 MediatR 和 IRequest<Guid> 集成到 WpfPowerTester 项目中,实现了以下改进:

  • 命令和查询:使用 StartProjectCommand 等命令,明确请求和响应类型。
  • 事件解耦:用 INotification 替换静态 DomainEventPublisher,支持多处理程序。
  • 架构清晰:应用层通过 MediatR 调用领域逻辑,保持 DDD 分层。
  • 可扩展性:新增命令、查询和事件只需定义新类,无需修改现有代码。

建议进一步实现图表服务(ChartService)和设备模型(Device),并添加管道行为(如日志、验证)以增强功能。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐