·
Last changedVersion:
This version passed the hardening gate. Hardening applied 9 targeted changes and preserved all other text. Classified findings before hardening: 0. Classified findings closed: 0. Classified findings remaining: 0. Unclassified scanner findings remaining: 0. Review findings closed: 6. Review findings remaining: 3.
None
None
Expand | File | Group | Subtype | Severity | Status |
|---|
No findings available
0 findings
Pub/sub messaging for ViewModels (or any objects) without forcing a shared
reference graph. Part of CommunityToolkit.Mvvm 8.x.
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 |
|---|---|
| Default. Recipients held weakly — eligible for GC even while registered. Internal trimming runs during full GCs; no manual |
| Profiler shows the messenger is hot and allocation matters. Recipients are pinned until you |
Custom | 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:
This async CurrentUserRequest is an alternative to the sync definition
above. Declare only one of the two in a project, or give the async type a
different name if you need both.
CollectionRequestMessage<T> and AsyncCollectionRequestMessage<T> collect
a Reply from every responding recipient:
Only recipients registered for OpenDocumentsRequest reply, each calling
Reply once per item. When no recipient is registered, the result is empty.
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. Set
IsActive = true in the view activation hook as shown in Lifecycle, or
call Messenger.RegisterAll(this) manually.DispatcherQueue.TryEnqueue / Dispatcher.BeginInvoke).Inject the appropriate IMessenger into the ViewModel constructor:
Caution: the scoped line registers only the concrete WindowScopedMessenger
type, not IMessenger. With the registrations above, a ViewModel that asks
for IMessenger receives the app-wide WeakReferenceMessenger.Default, so
broadcasts reach every window. Desktop DI does not create a scope per window
for you. To isolate broadcasts to a single window, create one messenger
instance per window, register it as IMessenger inside a scope you create
for that window, and pass that same instance to every ViewModel of that
window. This is useful for multi-window desktop apps such as WinUI 3, WPF,
MAUI desktop and Avalonia.
| Topic | File |
|---|---|
Full deep dive (more channel/lifecycle examples, diagnostics) |
External:
WeakReferenceMessenger API: https://learn.microsoft.com/en-us/dotnet/api/communitytoolkit.mvvm.messaging.weakreferencemessengerGet started with chainctlchainctl skill install public/github/mvvm-toolkit-messenger:latest
Bring Chainguard's hardening pipeline to your first-party skills — custom rulesets, automated remediation, full audit trail.
skills.cgr.dev/public/github.com/github/skills/mvvm-toolkit-messenger:latest
MIT
HARDENING.md
LICENSE
SKILL.md
references/messenger-patterns.md