npx skills add ...
npx skills add github/awesome-copilot --skill mvvm-toolkit-messenger
npx skills add github/awesome-copilot --skill mvvm-toolkit-messenger
CommunityToolkit.Mvvm Messenger pub/sub for decoupled communication between ViewModels (or any objects). Covers WeakReferenceMessenger vs StrongReferenceMessenger, IRecipient<TMessage>, RequestMessage<T> / AsyncRequestMessage<T> / CollectionRequestMessage<T>, ValueChangedMessage<T>, channels (tokens), and the ObservableRecipient activation lifecycle. Use across WPF, WinUI 3, .NET MAUI, Uno, and Avalonia.
Pub/sub messaging for ViewModels (or any objects) without forcing a shared
reference graph. Part of CommunityToolkit.Mvvm 8.x.
TL;DR. Default to
WeakReferenceMessenger.Default. Register handlers with the(recipient, message)lambda and thestaticmodifier so you never capturethis. Inherit fromObservableRecipientand toggleIsActiveat activation/deactivation to get automatic register/unregister.
For source generators, base classes, and commands see the mvvm-toolkit
skill. For DI wiring (registering an IMessenger instance), see
mvvm-toolkit-di.
| Type | When |
|---|---|
WeakReferenceMessenger.Default | Default. Recipients held weakly — eligible for GC even while registered. Internal trimming runs during full GCs; no manual Cleanup() needed. |
StrongReferenceMessenger.Default | Profiler shows the messenger is hot and allocation matters. Recipients are pinned until you Unregister. Forgetting unregistration leaks them. |
Custom IMessenger instance | Per-window/per-scope (e.g., one messenger per app window). Construct directly, inject via DI. |
ObservableRecipient's parameterless constructor uses
WeakReferenceMessenger.Default. Pass a different IMessenger to its
constructor to override.
The toolkit ships base classes; any class works.
The static modifier prevents accidental closure allocation and keeps
this out of the lambda — use the recipient parameter instead.
IRecipient<TMessage> interface styleObservableRecipient.OnActivated() calls Messenger.RegisterAll(this),
which subscribes every IRecipient<T> interface implemented by the type.
If you're not using ObservableRecipient, register manually:
Scope messages to a sub-system or window with a token (any equatable
value — int, string, Guid):
Messages sent without a token use the default shared channel — they are not delivered to channel-scoped recipients.
For ask-style scenarios where a recipient provides a value back to the
sender, use the RequestMessage<T> family.
The implicit conversion from CurrentUserRequest to User throws if no
recipient called Reply. Capture the message to check first:
CollectionRequestMessage<T> and AsyncCollectionRequestMessage<T> collect
a Reply from every responding recipient:
Even with WeakReferenceMessenger, unregister explicitly when a recipient
is being torn down — it trims dead entries and improves performance:
ObservableRecipient.OnDeactivated() does this automatically when
IsActive flips to false. Set it from your activation hook:
this in the lambda. (r, m) => OnX(m) implicitly
captures this; allocates a closure and confuses lifetime. Always use
(r, m) => r.OnX(m) with static.Unregister. With
StrongReferenceMessenger, recipients (and their entire object graph)
stay pinned forever. Either inherit from ObservableRecipient
(auto-unregisters in OnDeactivated) or call UnregisterAll(this).BaseMessage is
not invoked for DerivedMessage : BaseMessage. Register each
concrete type.WeakReferenceMessenger.Default
and registering via an injected per-window messenger means the message
never arrives. Use the same IMessenger everywhere (typically inject
it via ObservableRecipient(messenger)).OnActivated never runs. ObservableRecipient only registers
IRecipient<T> handlers when IsActive flips from false to true.DispatcherQueue.TryEnqueue / Dispatcher.BeginInvoke).Inject the appropriate IMessenger into the ViewModel constructor:
This isolates broadcasts to a single window — useful for multi-window desktop apps (WinUI 3, WPF, MAUI desktop, Avalonia).
| Topic | File |
|---|---|
| Full deep dive (more channel/lifecycle examples, diagnostics) | references/messenger-patterns.md |
External:
WeakReferenceMessenger API: https://learn.microsoft.com/en-us/dotnet/api/communitytoolkit.mvvm.messaging.weakreferencemessenger