CommunityToolkit.Mvvm 架构笔记:现代 MVVM、源生成器与工程化实践
2026/6/5 10:17:13 网站建设 项目流程

一、核心概念

术语说明
CommunityToolkit.Mvvm微软维护的现代 MVVM 工具包,适合新项目和旧项目迁移
ObservableObject最基础的可通知对象,封装INotifyPropertyChangedINotifyPropertyChanging
ObservableRecipientObservableObject基础上增加消息接收能力,适合需要订阅消息的 ViewModel
RelayCommand同步命令实现,适合按钮点击、菜单切换等操作
AsyncRelayCommand异步命令实现,适合数据库、网络、文件等耗时操作
WeakReferenceMessenger基于弱引用的消息总线,减少订阅对象因忘记注销而导致的内存泄漏风险
ObservableProperty源生成器特性,通过字段标记自动生成属性与通知逻辑
RelayCommand 特性源生成器特性,通过方法标记自动生成命令属性
Ioc.DefaultToolkit 提供的轻量 DI 访问入口,常与Microsoft.Extensions.DependencyInjection一起使用,但不是必选项
源生成器编译期自动生成样板代码,减少手写属性、命令与通知逻辑

如果把 MVVM Light 看作“传统 MVVM 教学版”,那么 CommunityToolkit.Mvvm 更像“现代 MVVM 工程版”:保留核心模式,但尽量用编译期生成替代重复模板代码。

二、常用操作

常用能力速查

操作/能力常见类型或语法说明
属性通知ObservableObjectSetProperty(...)基础属性变更通知
自动生成属性[ObservableProperty]通过字段生成完整属性与通知代码
同步命令RelayCommand[RelayCommand]绑定普通按钮和菜单操作
异步命令AsyncRelayCommand[RelayCommand] async Task绑定异步业务流程
消息通信WeakReferenceMessengerObservableRecipient用于跨模块解耦通知
依赖注入Ioc.Default+ServiceCollection用于注册服务和 ViewModel
输入校验ObservableValidator在 ViewModel 中组合数据校验能力

1. 安装与基础使用

// 安装 NuGet 包 // CommunityToolkit.Mvvm ​ using CommunityToolkit.Mvvm.ComponentModel; ​ namespace Demo.ViewModels; ​ public partial class LoginViewModel : ObservableObject { [ObservableProperty] private string account = string.Empty; ​ [ObservableProperty] private string password = string.Empty; }

说明:

  • 标记为partial是因为源生成器会在编译期为当前类型补充成员。

  • [ObservableProperty]会根据字段名生成标准 PascalCase 属性,如account->Account

  • 生成的属性内部会自动调用通知逻辑,通常不需要再手写SetProperty

2. 手写属性通知与源生成器写法对比

方式写法特点适用场景
手写SetProperty可读性直接、行为清晰需要自定义细节较多时
[ObservableProperty]样板代码最少常规 ViewModel 属性首选
using CommunityToolkit.Mvvm.ComponentModel; ​ namespace Demo.ViewModels; ​ public class CustomerViewModel : ObservableObject { private string name = string.Empty; ​ public string Name { get => name; set => SetProperty(ref name, value); } } using CommunityToolkit.Mvvm.ComponentModel; ​ namespace Demo.ViewModels; ​ public partial class CustomerViewModel : ObservableObject { [ObservableProperty] private string name = string.Empty; }

典型收益:

  • 减少重复字段/属性/通知模板代码。

  • 更适合拥有大量表单字段、筛选条件、状态字段的 ViewModel。

  • 相比 MVVM Light 手写RaisePropertyChanged()更整洁。

3. 自动生成命令

using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using System.Threading.Tasks; ​ namespace Demo.ViewModels; ​ public partial class MainViewModel : ObservableObject { [ObservableProperty] private string currentPage = "Home"; ​ [RelayCommand] private void OpenPage(string pageName) { CurrentPage = pageName; } ​ [RelayCommand] private async Task LoadAsync() { await Task.Delay(300); } }

生成结果的理解方式:

  • OpenPage会生成一个可绑定的OpenPageCommand

  • LoadAsync会生成一个可绑定的异步命令属性,适合在界面中直接绑定加载、刷新、保存等异步操作。

  • 对于多数 CRUD、页面切换、搜索、保存、刷新操作,这种写法明显比传统手写命令更简洁。

4. 异步命令与 UI 响应性

using CommunityToolkit.Mvvm.Input; ​ namespace Demo.ViewModels; ​ public partial class UserListViewModel : ObservableObject { private readonly IUserService userService; ​ public UserListViewModel(IUserService userService) { this.userService = userService; RefreshUsersCommand = new AsyncRelayCommand(RefreshUsersAsync); } ​ public IAsyncRelayCommand RefreshUsersCommand { get; } ​ private async Task RefreshUsersAsync() { Users = await userService.GetUsersAsync(); } ​ [ObservableProperty] private IReadOnlyList<UserDto> users = Array.Empty<UserDto>(); }

说明:

  • 在现代 MVVM 项目中,数据库、HTTP、文件、串口等耗时任务都更适合放入异步命令。

  • AsyncRelayCommand能避免把耗时逻辑直接塞进同步命令造成 UI 假死。

  • 这也是 CommunityToolkit.Mvvm 相比老框架更贴近现代 .NET 开发习惯的地方。

5. 使用弱引用消息通信

using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging.Messages; ​ namespace Demo.Messages; ​ public sealed class LoginUserChangedMessage : ValueChangedMessage<UserDto> { public LoginUserChangedMessage(UserDto value) : base(value) { } } using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; ​ namespace Demo.ViewModels; ​ public partial class LoginViewModel : ObservableObject { [RelayCommand] private void Login() { var currentUser = new UserDto { Account = Account }; WeakReferenceMessenger.Default.Send(new LoginUserChangedMessage(currentUser)); } } using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; ​ namespace Demo.ViewModels; ​ public partial class HeaderViewModel : ObservableRecipient { [ObservableProperty] private string currentAccount = string.Empty; ​ public HeaderViewModel() { IsActive = true; } ​ protected override void OnActivated() { WeakReferenceMessenger.Default.Register<LoginUserChangedMessage>(this, static (recipient, message) => { ((HeaderViewModel)recipient).CurrentAccount = message.Value.Account; }); } }

与 MVVM Light 的差异点:

  • MVVM Light 常用Messenger.Default

  • Toolkit 推荐WeakReferenceMessenger.Default

  • 使用ObservableRecipient时,需要确保对象已进入激活状态,例如设置IsActive = true,这样OnActivated()中的注册逻辑才会执行。

  • 弱引用设计可以降低忘记取消注册带来的内存泄漏风险。

6. 依赖注入与 ViewModel 注册

using CommunityToolkit.Mvvm.DependencyInjection; using Microsoft.Extensions.DependencyInjection; var services = new ServiceCollection(); services.AddSingleton<IUserService, UserService>(); services.AddTransient<LoginViewModel>(); services.AddTransient<MainViewModel>(); services.AddTransient<UserListViewModel>(); Ioc.Default.ConfigureServices(services.BuildServiceProvider()); using CommunityToolkit.Mvvm.DependencyInjection; using System.Windows; public partial class LoginWindow : Window { public LoginWindow() { InitializeComponent(); DataContext = Ioc.Default.GetRequiredService<LoginViewModel>(); } }

说明:

  • 这一模式可视为对 MVVM Light 中SimpleIoc + ViewModelLocator的现代替代。

  • 现在更常见的做法不是继续依赖ServiceLocator风格,而是使用Microsoft.Extensions.DependencyInjection统一管理注册与解析。

  • 对 WPF 而言,即使不强制上 ASP.NET Core 那套完整宿主,也可以单独使用ServiceCollection

  • Ioc.Default是方便访问容器的入口,但不是必须使用;如果项目已有自己的宿主或容器组织方式,也可以直接使用现有 DI 体系。

7. 表单校验与可观察验证

using CommunityToolkit.Mvvm.ComponentModel; using System.ComponentModel.DataAnnotations; namespace Demo.ViewModels; public partial class RegisterViewModel : ObservableValidator { [ObservableProperty] [NotifyDataErrorInfo] [Required(ErrorMessage = "账号不能为空")] [MinLength(4, ErrorMessage = "账号长度不能小于 4")] private string account = string.Empty; [RelayCommand] private void Submit() { ValidateAllProperties(); if (HasErrors) { return; } // 保存逻辑 } }

适用场景:

  • 登录、注册、筛选条件、设备参数配置等表单型界面。

  • 比单纯在命令中if/else判断更利于统一管理验证规则。

8. 从 MVVM Light 迁移时的类型映射

MVVM LightCommunityToolkit.Mvvm说明
ViewModelBaseObservableObject/ObservableRecipient是否需要消息接收,决定选哪个基类
RelayCommandRelayCommand/AsyncRelayCommandToolkit 补足了更现代的异步命令体验
MessengerWeakReferenceMessenger推荐使用弱引用消息总线
SimpleIocIoc.Default+ServiceCollection更推荐与Microsoft.Extensions.DependencyInjection协同
手写RaisePropertyChanged[ObservableProperty]源生成器减少模板代码
手写命令属性[RelayCommand]方法即命令,编译期生成命令成员

9. 在 WPF 实际项目中的推荐组织方式

// 目录示例 // Models/ // Services/ // ViewModels/ // Views/ // Messages/ // Converters/ // Behaviors/ // 推荐原则 // 1. 业务状态放 ViewModel。 // 2. 数据访问放 Service/Repository。 // 3. 页面切换通过导航服务或状态属性,而不是大量反射拼接字符串。 // 4. 跨模块通知使用消息对象,不直接互相持有引用。 // 5. 重复属性和命令优先交给源生成器处理。

三、问题排查

错误1:使用[ObservableProperty]后没有生成属性

  • 现象:编译通过前或 IDE 中看不到AccountPassword等生成属性,绑定报错。

  • 原因:类型没有声明为partial,或者项目没有正确引用CommunityToolkit.Mvvm

  • 解决:将类改为partial,确认 NuGet 包正常安装并重新生成项目。

public partial class LoginViewModel : ObservableObject { [ObservableProperty] private string account = string.Empty; }

错误2:异步按钮点击后界面卡顿

  • 现象:点击“加载”“查询”“登录”等按钮后窗口短暂无响应。

  • 原因:仍在同步命令中直接执行耗时操作。

  • 解决:改为AsyncRelayCommand[RelayCommand] async Task,将 I/O 操作异步化。

错误3:消息接收器似乎没有收到消息

  • 现象:发送端调用WeakReferenceMessenger.Default.Send(...)后,接收端没有更新。

  • 原因:接收端没有注册消息,或者ObservableRecipient未激活。

  • 解决:显式注册消息;如果继承ObservableRecipient,需要确保对象已激活,例如设置IsActive = true

WeakReferenceMessenger.Default.Register<LoginUserChangedMessage>(this, static (recipient, message) => { ((HeaderViewModel)recipient).CurrentAccount = message.Value.Account; });

错误4:从 MVVM Light 直接复制ViewModelLocator用法后结构变得混乱

  • 现象:新项目一边写ViewModelLocator,一边又使用ServiceCollection,注册入口分散。

  • 原因:把旧框架习惯原封不动搬到新框架中。

  • 解决:优先统一到Ioc.Default + ServiceCollection或更完整的宿主启动模式,不必强依赖传统 Locator 风格。

错误5:表单验证规则写了但界面没有提示

  • 现象[Required]等特性存在,但保存时没有触发错误状态。

  • 原因:没有调用ValidateAllProperties(),或属性未启用错误通知相关特性,或界面没有正确绑定验证错误显示。

  • 解决:在提交命令中先校验,并保证使用ObservableValidator、相关验证特性,以及界面的验证绑定配置正确。

如果你的目标是“日常项目长期使用 + 博客输出 + 现代 .NET 客户端开发”,CommunityToolkit.Mvvm 通常比 MVVM Light 更值得作为主力方案:一方面保留了 MVVM 的核心抽象,另一方面用源生成器、异步命令和现代 DI 习惯大幅减少样板代码。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询