{"id":"0b2a38f9-e7cd-42e4-b351-10797da20f95","shortId":"CJxMkt","kind":"skill","title":"mvvm-toolkit-messenger","tagline":"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<","description":"# CommunityToolkit.Mvvm Messenger\n\nPub/sub messaging for ViewModels (or any objects) without forcing a shared\nreference graph. Part of `CommunityToolkit.Mvvm` 8.x.\n\n> **TL;DR.** Default to `WeakReferenceMessenger.Default`. Register handlers\n> with the `(recipient, message)` lambda and the `static` modifier so you\n> never capture `this`. Inherit from `ObservableRecipient` and toggle\n> `IsActive` at activation/deactivation to get automatic register/unregister.\n\n---\n\n## When to use this skill\n\n- Two or more ViewModels need to react to an event (login, theme change,\n  save, navigation) without holding references to each other\n- A ViewModel needs to ask another VM for a value (request/reply)\n- You're scoping events to a sub-system or window with channel tokens\n- Diagnosing \"my handler never fires\" or weak-reference recipient lifetime\n  problems\n\nFor source generators, base classes, and commands see the **`mvvm-toolkit`**\nskill. For DI wiring (registering an `IMessenger` instance), see\n**`mvvm-toolkit-di`**.\n\n---\n\n## Choose an implementation\n\n| Type | When |\n|------|------|\n| `WeakReferenceMessenger.Default` | **Default.** Recipients held weakly — eligible for GC even while registered. Internal trimming runs during full GCs; no manual `Cleanup()` needed. |\n| `StrongReferenceMessenger.Default` | Profiler shows the messenger is hot and allocation matters. Recipients are pinned until you `Unregister`. Forgetting unregistration leaks them. |\n| Custom `IMessenger` instance | Per-window/per-scope (e.g., one messenger per app window). Construct directly, inject via DI. |\n\n`ObservableRecipient`'s parameterless constructor uses\n`WeakReferenceMessenger.Default`. Pass a different `IMessenger` to its\nconstructor to override.\n\n---\n\n## Define a message\n\nThe toolkit ships base classes; any class works.\n\n```csharp\nusing CommunityToolkit.Mvvm.Messaging.Messages;\n\n// Single-payload broadcast\npublic sealed class LoggedInUserChangedMessage(User user)\n    : ValueChangedMessage<User>(user);\n\n// Custom shape (records are great for this)\npublic sealed record ThemeChangedMessage(AppTheme NewTheme);\n\n// Empty signal\npublic sealed record RefreshRequestedMessage;\n```\n\n---\n\n## Register a recipient\n\n### Lambda style (recommended)\n\n```csharp\nWeakReferenceMessenger.Default.Register<MyViewModel, ThemeChangedMessage>(\n    this,\n    static (recipient, message) => recipient.OnThemeChanged(message.NewTheme));\n```\n\nThe `static` modifier prevents accidental closure allocation and keeps\n`this` out of the lambda — use the `recipient` parameter instead.\n\n### `IRecipient<TMessage>` interface style\n\n```csharp\npublic sealed class MyViewModel : ObservableRecipient,\n    IRecipient<ThemeChangedMessage>,\n    IRecipient<RefreshRequestedMessage>\n{\n    public void Receive(ThemeChangedMessage message) { /* ... */ }\n    public void Receive(RefreshRequestedMessage message) { /* ... */ }\n}\n```\n\n`ObservableRecipient.OnActivated()` calls `Messenger.RegisterAll(this)`,\nwhich subscribes every `IRecipient<T>` interface implemented by the type.\nIf you're not using `ObservableRecipient`, register manually:\n\n```csharp\nWeakReferenceMessenger.Default.RegisterAll(this);\n```\n\n---\n\n## Send a message\n\n```csharp\nWeakReferenceMessenger.Default.Send(new ThemeChangedMessage(AppTheme.Dark));\n\n// Empty payloads use the parameterless overload:\nWeakReferenceMessenger.Default.Send<RefreshRequestedMessage>();\n```\n\n---\n\n## Channels (tokens)\n\nScope messages to a sub-system or window with a token (any equatable\nvalue — `int`, `string`, `Guid`):\n\n```csharp\nconst int LeftPaneChannel = 1;\n\nWeakReferenceMessenger.Default.Register<MyViewModel, RefreshRequestedMessage, int>(\n    this, LeftPaneChannel,\n    static (r, _) => r.RefreshLeft());\n\nWeakReferenceMessenger.Default.Send(new RefreshRequestedMessage(), LeftPaneChannel);\n```\n\nMessages sent without a token use the default shared channel — they are\n**not** delivered to channel-scoped recipients.\n\n---\n\n## Request / reply\n\nFor ask-style scenarios where a recipient provides a value back to the\nsender, use the `RequestMessage<T>` family.\n\n### Sync request\n\n```csharp\npublic sealed class CurrentUserRequest : RequestMessage<User> { }\n\nWeakReferenceMessenger.Default.Register<UserService, CurrentUserRequest>(\n    this,\n    static (r, m) => m.Reply(r.CurrentUser));\n\nUser user = WeakReferenceMessenger.Default.Send<CurrentUserRequest>();\n```\n\nThe implicit conversion from `CurrentUserRequest` to `User` throws if no\nrecipient called `Reply`. Capture the message to check first:\n\n```csharp\nvar request = WeakReferenceMessenger.Default.Send<CurrentUserRequest>();\nif (request.HasReceivedResponse)\n    User user = request.Response;\n```\n\n### Async request\n\n```csharp\npublic sealed class CurrentUserRequest : AsyncRequestMessage<User> { }\n\nWeakReferenceMessenger.Default.Register<UserService, CurrentUserRequest>(\n    this,\n    static (r, m) => m.Reply(r.GetCurrentUserAsync()));\n\nUser user = await WeakReferenceMessenger.Default.Send<CurrentUserRequest>();\n```\n\n### Collection requests (fan-in)\n\n`CollectionRequestMessage<T>` and `AsyncCollectionRequestMessage<T>` collect\na `Reply` from every responding recipient:\n\n```csharp\npublic sealed class OpenDocumentsRequest : CollectionRequestMessage<Document> { }\n\nvar docs = WeakReferenceMessenger.Default.Send<OpenDocumentsRequest>();\nforeach (Document doc in docs) { /* ... */ }\n```\n\n---\n\n## Lifecycle\n\nEven with `WeakReferenceMessenger`, unregister explicitly when a recipient\nis being torn down — it trims dead entries and improves performance:\n\n```csharp\nWeakReferenceMessenger.Default.Unregister<ThemeChangedMessage>(this);\nWeakReferenceMessenger.Default.Unregister<ThemeChangedMessage, int>(this, LeftPaneChannel);\nWeakReferenceMessenger.Default.UnregisterAll(this);\n```\n\n`ObservableRecipient.OnDeactivated()` does this automatically when\n`IsActive` flips to `false`. Set it from your activation hook:\n\n```csharp\nprotected override void OnNavigatedTo(NavigationEventArgs e)\n{\n    base.OnNavigatedTo(e);\n    ViewModel.IsActive = true;\n}\n\nprotected override void OnNavigatedFrom(NavigationEventArgs e)\n{\n    ViewModel.IsActive = false;\n    base.OnNavigatedFrom(e);\n}\n```\n\n---\n\n## Common pitfalls\n\n1. **Capturing `this` in the lambda.** `(r, m) => OnX(m)` implicitly\n   captures `this`; allocates a closure and confuses lifetime. Always use\n   `(r, m) => r.OnX(m)` with `static`.\n2. **Strong-ref recipients without `Unregister`.** With\n   `StrongReferenceMessenger`, recipients (and their entire object graph)\n   stay pinned forever. Either inherit from `ObservableRecipient`\n   (auto-unregisters in `OnDeactivated`) or call `UnregisterAll(this)`.\n3. **Inherited message types.** A handler registered for `BaseMessage` is\n   **not** invoked for `DerivedMessage : BaseMessage`. Register each\n   concrete type.\n4. **Wrong messenger instance.** Sending via `WeakReferenceMessenger.Default`\n   and registering via an injected per-window messenger means the message\n   never arrives. Use the same `IMessenger` everywhere (typically inject\n   it via `ObservableRecipient(messenger)`).\n5. **`OnActivated` never runs.** `ObservableRecipient` only registers\n   `IRecipient<T>` handlers when `IsActive` flips from `false` to `true`.\n6. **Cross-thread updates.** The messenger is thread-agnostic. If a\n   handler updates UI, marshal manually\n   (`DispatcherQueue.TryEnqueue` / `Dispatcher.BeginInvoke`).\n\n---\n\n## Multiple messengers (per-window scoping)\n\n```csharp\nservices.AddSingleton<IMessenger>(WeakReferenceMessenger.Default); // app-wide\nservices.AddScoped<WindowScopedMessenger>();                       // per-window\n```\n\nInject the appropriate `IMessenger` into the ViewModel constructor:\n\n```csharp\npublic sealed partial class WindowViewModel(IMessenger messenger)\n    : ObservableRecipient(messenger) { }\n```\n\nThis isolates broadcasts to a single window — useful for multi-window\ndesktop apps (WinUI 3, WPF, MAUI desktop, Avalonia).\n\n---\n\n## References\n\n| Topic | File |\n|-------|------|\n| Full deep dive (more channel/lifecycle examples, diagnostics) | [`references/messenger-patterns.md`](references/messenger-patterns.md) |\n\nExternal:\n\n- Messenger docs: <https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger>\n- `WeakReferenceMessenger` API: <https://learn.microsoft.com/en-us/dotnet/api/communitytoolkit.mvvm.messaging.weakreferencemessenger>\n- Source: <https://github.com/CommunityToolkit/dotnet>","tags":["mvvm","toolkit","messenger","awesome","copilot","github","agent-skills","agents","custom-agents","github-copilot","hacktoberfest","prompt-engineering"],"capabilities":["skill","source-github","skill-mvvm-toolkit-messenger","topic-agent-skills","topic-agents","topic-awesome","topic-custom-agents","topic-github-copilot","topic-hacktoberfest","topic-prompt-engineering"],"categories":["awesome-copilot"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/github/awesome-copilot/mvvm-toolkit-messenger","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add github/awesome-copilot","source_repo":"https://github.com/github/awesome-copilot","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 33270 github stars · SKILL.md body (8,393 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:52:18.041Z","embedding":null,"createdAt":"2026-05-11T06:52:41.248Z","updatedAt":"2026-05-18T18:52:18.041Z","lastSeenAt":"2026-05-18T18:52:18.041Z","tsv":"'/communitytoolkit/dotnet':854 '/en-us/dotnet/api/communitytoolkit.mvvm.messaging.weakreferencemessenger':850 '/en-us/dotnet/communitytoolkit/mvvm/messenger':845 '/per-scope':218 '1':409,629 '2':656 '3':687,823 '4':706 '5':738 '6':754 '8':43 'accident':310 'activ':604 'activation/deactivation':73 'agnost':764 'alloc':200,312,642 'alway':648 'anoth':109 'api':847 'app':223,784,821 'app-wid':783 'appropri':792 'appthem':282 'apptheme.dark':377 'arriv':726 'ask':108,446 'ask-styl':445 'async':511 'asynccollectionrequestmessag':539 'asyncrequestmessag':22,518 'auto':679 'auto-unregist':678 'automat':76,594 'avalonia':827 'await':530 'back':455 'base':144,251 'base.onnavigatedfrom':625 'base.onnavigatedto':613 'basemessag':695,701 'broadcast':262,810 'call':347,494,684 'captur':64,496,630,640 'chang':95 'channel':127,385,432,439 'channel-scop':438 'channel/lifecycle':835 'check':500 'choos':166 'class':145,252,254,265,331,468,516,550,802 'cleanup':190 'closur':311,644 'collect':532,540 'collectionrequestmessag':23,537,552 'command':147 'common':627 'communic':10 'communitytoolkit.mvvm':5,25,42 'communitytoolkit.mvvm.messaging.messages':258 'concret':704 'confus':646 'const':406 'construct':225 'constructor':233,242,797 'convers':485 'cover':16 'cross':756 'cross-thread':755 'csharp':256,296,328,367,373,405,465,502,513,547,581,606,780,798 'currentuserrequest':469,473,487,517,521 'custom':212,271 'dead':576 'decoupl':9 'deep':832 'default':47,172,430 'defin':245 'deliv':436 'derivedmessag':700 'desktop':820,826 'di':155,165,229 'diagnos':129 'diagnost':837 'differ':238 'direct':226 'dispatcher.begininvoke':773 'dispatcherqueue.tryenqueue':772 'dive':833 'doc':554,558,560,842 'document':557 'dr':46 'e':612,614,622,626 'e.g':219 'either':674 'elig':176 'empti':284,378 'entir':668 'entri':577 'equat':400 'even':179,562 'event':92,118 'everi':352,544 'everywher':731 'exampl':836 'explicit':566 'extern':840 'fals':599,624,751 'famili':462 'fan':535 'fan-in':534 'file':830 'fire':133 'first':501 'flip':597,749 'forc':35 'foreach':556 'forev':673 'forget':208 'full':186,831 'gc':178 'gcs':187 'generat':143 'get':75 'github.com':853 'github.com/communitytoolkit/dotnet':852 'graph':39,670 'great':275 'guid':404 'handler':51,131,692,746,767 'held':174 'hold':99 'hook':605 'hot':198 'imesseng':159,213,239,730,793,804 'implement':168,355 'implicit':484,639 'improv':579 'inherit':66,675,688 'inject':227,717,733,790 'instanc':160,214,709 'instead':324 'int':402,407,413,586 'interfac':326,354 'intern':182 'invok':698 'irecipi':20,325,334,335,353,745 'isact':71,596,748 'isol':809 'keep':314 'lambda':56,293,319,634 'leak':210 'learn.microsoft.com':844,849 'learn.microsoft.com/en-us/dotnet/api/communitytoolkit.mvvm.messaging.weakreferencemessenger':848 'learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger':843 'leftpanechannel':408,415,422,588 'lifecycl':561 'lifetim':139,647 'loggedinuserchangedmessag':266 'login':93 'm':477,525,636,638,651,653 'm.reply':478,526 'manual':189,366,771 'marshal':770 'matter':201 'maui':825 'mean':722 'messag':28,55,247,303,340,345,372,388,423,498,689,724 'message.newtheme':305 'messeng':4,6,26,196,221,708,721,737,760,775,805,807,841 'messenger.registerall':348 'modifi':60,308 'multi':818 'multi-window':817 'multipl':774 'mvvm':2,151,163 'mvvm-toolkit':150 'mvvm-toolkit-di':162 'mvvm-toolkit-messeng':1 'myviewmodel':298,332,411 'navig':97 'navigationeventarg':611,621 'need':87,106,191 'never':63,132,725,740 'new':375,420 'newthem':283 'object':15,33,669 'observablerecipi':68,230,333,364,677,736,742,806 'observablerecipient.onactivated':346 'observablerecipient.ondeactivated':591 'onactiv':739 'ondeactiv':682 'one':220 'onnavigatedfrom':620 'onnavigatedto':610 'onx':637 'opendocumentsrequest':551 'overload':383 'overrid':244,608,618 'paramet':323 'parameterless':232,382 'part':40 'partial':801 'pass':236 'payload':261,379 'per':216,222,719,777,788 'per-window':215,718,776,787 'perform':580 'pin':204,672 'pitfal':628 'prevent':309 'problem':140 'profil':193 'protect':607,617 'provid':452 'pub/sub':7,27 'public':263,278,286,329,336,341,466,514,548,799 'r':417,476,524,635,650 'r.currentuser':479 'r.getcurrentuserasync':527 'r.onx':652 'r.refreshleft':418 're':116,361 'react':89 'receiv':338,343 'recipi':54,138,173,202,292,302,322,441,451,493,546,569,660,665 'recipient.onthemechanged':304 'recommend':295 'record':273,280,288 'ref':659 'refer':38,100,137,828 'references/messenger-patterns.md':838,839 'refreshrequestedmessag':289,344,412,421 'regist':50,157,181,290,365,693,702,714,744 'register/unregister':77 'repli':443,495,542 'request':442,464,504,512,533 'request.hasreceivedresponse':507 'request.response':510 'request/reply':114 'requestmessag':21,461,470 'respond':545 'run':184,741 'save':96 'scenario':448 'scope':117,387,440,779 'seal':264,279,287,330,467,515,549,800 'see':148,161 'send':370,710 'sender':458 'sent':424 'services.addscoped':786 'services.addsingleton':781 'set':600 'shape':272 'share':37,431 'ship':250 'show':194 'signal':285 'singl':260,813 'single-payload':259 'skill':82,153 'skill-mvvm-toolkit-messenger' 'sourc':142,851 'source-github' 'static':59,301,307,416,475,523,655 'stay':671 'string':403 'strong':658 'strong-ref':657 'strongreferencemesseng':19,664 'strongreferencemessenger.default':192 'style':294,327,447 'sub':122,392 'sub-system':121,391 'subscrib':351 'sync':463 'system':123,393 'theme':94 'themechangedmessag':281,299,339,376,585 'thread':757,763 'thread-agnost':762 'throw':490 'tl':45 'toggl':70 'token':128,386,398,427 'toolkit':3,152,164,249 'topic':829 'topic-agent-skills' 'topic-agents' 'topic-awesome' 'topic-custom-agents' 'topic-github-copilot' 'topic-hacktoberfest' 'topic-prompt-engineering' 'torn':572 'trim':183,575 'true':616,753 'two':83 'type':169,358,690,705 'typic':732 'ui':769 'unregist':207,565,662,680 'unregisteral':685 'unregistr':209 'updat':758,768 'use':80,234,257,320,363,380,428,459,649,727,815 'user':267,268,270,480,481,489,508,509,528,529 'userservic':472,520 'valu':113,401,454 'valuechangedmessag':24,269 'var':503,553 'via':228,711,715,735 'viewmodel':12,30,86,105,796 'viewmodel.isactive':615,623 'vm':110 'void':337,342,609,619 'vs':18 'weak':136,175 'weak-refer':135 'weakreferencemesseng':17,564,846 'weakreferencemessenger.default':49,171,235,712,782 'weakreferencemessenger.default.register':297,410,471,519 'weakreferencemessenger.default.registerall':368 'weakreferencemessenger.default.send':374,384,419,482,505,531,555 'weakreferencemessenger.default.unregister':582,584 'weakreferencemessenger.default.unregisterall':589 'wide':785 'window':125,217,224,395,720,778,789,814,819 'windowviewmodel':803 'winui':822 'wire':156 'without':34,98,425,661 'work':255 'wpf':824 'wrong':707 'x':44","prices":[{"id":"5ac6a406-1c1a-498d-bf11-5f359212672c","listingId":"0b2a38f9-e7cd-42e4-b351-10797da20f95","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"github","category":"awesome-copilot","install_from":"skills.sh"},"createdAt":"2026-05-11T06:52:41.248Z"}],"sources":[{"listingId":"0b2a38f9-e7cd-42e4-b351-10797da20f95","source":"github","sourceId":"github/awesome-copilot/mvvm-toolkit-messenger","sourceUrl":"https://github.com/github/awesome-copilot/tree/main/skills/mvvm-toolkit-messenger","isPrimary":false,"firstSeenAt":"2026-05-11T06:52:41.248Z","lastSeenAt":"2026-05-18T18:52:18.041Z"}],"details":{"listingId":"0b2a38f9-e7cd-42e4-b351-10797da20f95","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"github","slug":"mvvm-toolkit-messenger","github":{"repo":"github/awesome-copilot","stars":33270,"topics":["agent-skills","agents","ai","awesome","custom-agents","github-copilot","hacktoberfest","prompt-engineering"],"license":"mit","html_url":"https://github.com/github/awesome-copilot","pushed_at":"2026-05-18T01:26:59Z","description":"Community-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot.","skill_md_sha":"d9ab5e85d029df6264c8237e8c52a66adcd1a6ad","skill_md_path":"skills/mvvm-toolkit-messenger/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/github/awesome-copilot/tree/main/skills/mvvm-toolkit-messenger"},"layout":"multi","source":"github","category":"awesome-copilot","frontmatter":{"name":"mvvm-toolkit-messenger","description":"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."},"skills_sh_url":"https://skills.sh/github/awesome-copilot/mvvm-toolkit-messenger"},"updatedAt":"2026-05-18T18:52:18.041Z"}}