{"id":"be0cda3b-01f3-4713-9e34-397911c170b7","shortId":"76WfTQ","kind":"skill","title":"mvvm-toolkit","tagline":"CommunityToolkit.Mvvm (the MVVM Toolkit) core: source generators ([ObservableProperty], [RelayCommand], [NotifyPropertyChangedFor], [NotifyCanExecuteChangedFor], [NotifyDataErrorInfo]), base classes (ObservableObject / ObservableValidator / ObservableRecipient), commands (RelayCo","description":"# CommunityToolkit.Mvvm (core)\n\nUse this skill when authoring or reviewing ViewModels, properties,\ncommands, or validation in apps that use `CommunityToolkit.Mvvm` 8.x.\n\n> **Companion skills.** Load **`mvvm-toolkit-messenger`** for `IMessenger`\n> pub/sub patterns. Load **`mvvm-toolkit-di`** for\n> `Microsoft.Extensions.DependencyInjection` integration.\n\n> **Quick recap.** `[ObservableProperty]` on private fields in `partial`\n> classes; `[RelayCommand]` on instance methods; inherit from\n> `ObservableObject` (or `ObservableValidator` for input forms,\n> `ObservableRecipient` when using `IMessenger`).\n\n---\n\n## Package & setup\n\n```xml\n<ItemGroup>\n  <PackageReference Include=\"CommunityToolkit.Mvvm\" Version=\"8.*\" />\n</ItemGroup>\n```\n\nTargets: `netstandard2.0`, `netstandard2.1`, `net6.0`+. Works on .NET, .NET\nFramework, Mono. Source generators ship in the same NuGet — no extra\nanalyzer reference required.\n\nNamespaces:\n\n```csharp\nusing CommunityToolkit.Mvvm.ComponentModel;   // ObservableObject, [ObservableProperty]\nusing CommunityToolkit.Mvvm.Input;             // [RelayCommand], RelayCommand, AsyncRelayCommand\n```\n\n> **Universal rule.** Every type that uses `[ObservableProperty]` or\n> `[RelayCommand]` — and every enclosing type, if nested — must be\n> declared `partial`. Without it, the generators emit\n> `MVVMTK0008` / `MVVMTK0042`.\n\n---\n\n## Source generators cheat sheet\n\n| Attribute | Applied to | Generates |\n|-----------|-----------|-----------|\n| `[ObservableProperty]` | private field | Public `INotifyPropertyChanged` property + `OnXxxChanging`/`OnXxxChanged` partial-method hooks |\n| `[NotifyPropertyChangedFor(nameof(Other))]` | observable field | Also raises `PropertyChanged` for the listed property |\n| `[NotifyCanExecuteChangedFor(nameof(MyCommand))]` | observable field | Calls `MyCommand.NotifyCanExecuteChanged()` on change |\n| `[NotifyDataErrorInfo]` | observable field on `ObservableValidator` | Calls `ValidateProperty(value)` from the setter |\n| `[NotifyPropertyChangedRecipients]` | observable field on `ObservableRecipient` | `Broadcast(old, new)` after the change |\n| `[RelayCommand]` | instance method | Lazy `RelayCommand` / `AsyncRelayCommand` exposed as `IRelayCommand` / `IAsyncRelayCommand` |\n| `[RelayCommand(CanExecute = nameof(CanX))]` | instance method | Wires `CanExecute` to a method or property |\n| `[RelayCommand(IncludeCancelCommand = true)]` | async method with `CancellationToken` | Also generates `XxxCancelCommand` |\n| `[RelayCommand(AllowConcurrentExecutions = true)]` | async method | Allows queued/parallel invocations (default disables while running) |\n| `[RelayCommand(FlowExceptionsToTaskScheduler = true)]` | async method | Surfaces exceptions via `ExecutionTask` instead of awaiting and rethrowing |\n| `[property: SomeAttr]` | observable field or `[RelayCommand]` method | Forwards `SomeAttr` onto the generated property (e.g., `[JsonIgnore]`) |\n\n**Naming.** Field `name` / `_name` / `m_name` → `Name`. Method `LoadAsync` →\n`LoadCommand` (the `Async` suffix is stripped; a leading `On` is also\nstripped).\n\nSee [`references/source-generators.md`](references/source-generators.md) for\nthe full attribute reference with generated-code samples.\n\n---\n\n## ViewModel patterns\n\n### Simple observable property\n\n```csharp\npublic partial class ContactViewModel : ObservableObject\n{\n    [ObservableProperty]\n    private string? name;\n}\n```\n\n### Hooks: `OnXxxChanging` / `OnXxxChanged`\n\n```csharp\n[ObservableProperty]\nprivate string? name;\n\npartial void OnNameChanged(string? value) =>\n    Logger.LogInformation(\"Name changed to {Name}\", value);\n```\n\nBoth single-arg `(value)` and two-arg `(oldValue, newValue)` overloads\nare available. Implement only the ones you need; unimplemented hooks are\nelided by the compiler (zero runtime cost).\n\n### Dependent properties + dependent commands\n\n```csharp\n[ObservableProperty]\n[NotifyPropertyChangedFor(nameof(FullName))]\n[NotifyCanExecuteChangedFor(nameof(SaveCommand))]\nprivate string? firstName;\n\n[ObservableProperty]\n[NotifyPropertyChangedFor(nameof(FullName))]\n[NotifyCanExecuteChangedFor(nameof(SaveCommand))]\nprivate string? lastName;\n\npublic string FullName => $\"{FirstName} {LastName}\".Trim();\n```\n\n### Wrapping a non-observable model\n\n```csharp\npublic sealed class ObservableUser(User user) : ObservableObject\n{\n    public string Name\n    {\n        get => user.Name;\n        set => SetProperty(user.Name, value, user, (u, n) => u.Name = n);\n    }\n}\n```\n\nPass a static lambda (no captured state) to keep the call allocation-free.\n\n---\n\n## Commands\n\n```csharp\n[RelayCommand]\nprivate void Refresh() => Items.Reset();\n\n[RelayCommand]\nprivate async Task LoadAsync()\n{\n    foreach (var item in await service.GetItemsAsync())\n        Items.Add(item);\n}\n\n[RelayCommand(IncludeCancelCommand = true)]\nprivate async Task DownloadAsync(CancellationToken token)\n{\n    await using var stream = await http.GetStreamAsync(url, token);\n    // ...\n}\n\n[RelayCommand(CanExecute = nameof(CanSave))]\nprivate Task SaveAsync() => repo.SaveAsync(Name!);\n\nprivate bool CanSave() => !string.IsNullOrWhiteSpace(Name);\n```\n\nReach for manual `RelayCommand` / `AsyncRelayCommand` constructors only\nwhen you must own the command's lifetime explicitly or compose it from\nnon-trivial sources. The attribute style covers ~95% of cases.\n\nSee [`references/relaycommand-cookbook.md`](references/relaycommand-cookbook.md)\nfor sync / async / cancellable / concurrency / error-surfacing recipes.\n\n---\n\n## Base class selection\n\n| Base class | Use when |\n|------------|---------|\n| `ObservableObject` | Default. `INotifyPropertyChanged` + `INotifyPropertyChanging` + `SetProperty` overloads + `SetPropertyAndNotifyOnCompletion` for `Task` properties |\n| `ObservableValidator` | The VM needs `INotifyDataErrorInfo` (forms, settings input) |\n| `ObservableRecipient` | The VM sends or receives `IMessenger` messages — see the **`mvvm-toolkit-messenger`** skill |\n\nC# is single-inheritance: `ObservableValidator` and `ObservableRecipient`\nboth extend `ObservableObject`, so combining them requires composition\n(e.g., inject `IMessenger` into an `ObservableValidator`).\n\n---\n\n## Validation\n\n```csharp\nusing System.ComponentModel.DataAnnotations;\n\npublic sealed partial class RegistrationViewModel : ObservableValidator\n{\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required, MinLength(2), MaxLength(100)]\n    private string? name;\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required, EmailAddress]\n    private string? email;\n\n    [RelayCommand]\n    private void Submit()\n    {\n        ValidateAllProperties();\n        if (HasErrors) return;\n        // submit...\n    }\n}\n```\n\nOther entry points: `TrySetProperty`, `ValidateProperty(value, name)`,\n`ClearAllErrors()`, `GetErrors(propertyName)`. Custom rules support\n`[CustomValidation]` methods and custom `ValidationAttribute` subclasses.\n\nSee [`references/validation.md`](references/validation.md) for the full\nvalidator surface area.\n\n---\n\n## Top pitfalls\n\n1. **Forgetting `partial`.** Class (and every enclosing type) must be\n   `partial`. Compile error `MVVMTK0008` / `MVVMTK0042`.\n2. **PascalCase field name.** `[ObservableProperty] private string Name;`\n   collides with the generated property. Use `name`, `_name`, or `m_name`.\n3. **`async void` on `[RelayCommand]`.** The generator only wraps\n   `Task`-returning methods as `IAsyncRelayCommand`. `async void` becomes\n   a sync `RelayCommand` and exceptions are unobserved. Always return\n   `Task`.\n4. **Forgetting `[NotifyCanExecuteChangedFor]`.** The Save button stays\n   disabled even though `CanSave()` would now return `true`.\n5. **Mutating the same reference held by an `[ObservableProperty]`\n   field.** `EqualityComparer<T>.Default` returns `true`, no notification\n   fires. Replace the instance instead of mutating it.\n\nFor the full diagnostic table (`MVVMTK0xxx`) and more pitfalls, see\n[`references/troubleshooting.md`](references/troubleshooting.md).\n\n---\n\n## End-to-end mini walkthrough\n\nA two-pane Notes app demonstrating generators + commands +\n`[NotifyCanExecuteChangedFor]`:\n\n```csharp\npublic sealed partial class NoteViewModel(INotesService notes,\n    IMessenger messenger) : ObservableRecipient(messenger)\n{\n    [ObservableProperty]\n    [NotifyCanExecuteChangedFor(nameof(SaveCommand))]\n    [NotifyCanExecuteChangedFor(nameof(DeleteCommand))]\n    private string? filename;\n\n    [ObservableProperty]\n    [NotifyCanExecuteChangedFor(nameof(SaveCommand))]\n    private string? text;\n\n    [RelayCommand(CanExecute = nameof(CanSave))]\n    private Task SaveAsync()\n    {\n        Messenger.Send(new NoteSavedMessage(Filename!));\n        return notes.SaveAsync(Filename!, Text!);\n    }\n\n    [RelayCommand(CanExecute = nameof(CanDelete))]\n    private Task DeleteAsync() => notes.DeleteAsync(Filename!);\n\n    private bool CanSave() =>\n        !string.IsNullOrWhiteSpace(Filename) && !string.IsNullOrEmpty(Text);\n    private bool CanDelete() => !string.IsNullOrWhiteSpace(Filename);\n}\n```\n\nFor the full sample (DI wiring, View code-behind, XAML, unit tests), see\n[`references/end-to-end-walkthrough.md`](references/end-to-end-walkthrough.md).\n\n---\n\n## References & companion skills\n\n| Topic | Where |\n|-------|-------|\n| Source generator attribute reference | [`references/source-generators.md`](references/source-generators.md) |\n| RelayCommand recipes | [`references/relaycommand-cookbook.md`](references/relaycommand-cookbook.md) |\n| Validation deep dive | [`references/validation.md`](references/validation.md) |\n| Full Notes-app walkthrough | [`references/end-to-end-walkthrough.md`](references/end-to-end-walkthrough.md) |\n| `MVVMTK0xxx` diagnostics & pitfalls | [`references/troubleshooting.md`](references/troubleshooting.md) |\n| **Messenger pub/sub** | Companion skill: **`mvvm-toolkit-messenger`** |\n| **`Microsoft.Extensions.DependencyInjection` wiring** | Companion skill: **`mvvm-toolkit-di`** |\n\nExternal sources:\n\n- Toolkit overview: <https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/>\n- WinUI MVVM Toolkit tutorial: <https://learn.microsoft.com/en-us/windows/apps/tutorials/winui-mvvm-toolkit/intro>\n- Source: <https://github.com/CommunityToolkit/dotnet>\n- Samples: <https://github.com/CommunityToolkit/MVVM-Samples>","tags":["mvvm","toolkit","awesome","copilot","github","agent-skills","agents","custom-agents","github-copilot","hacktoberfest","prompt-engineering"],"capabilities":["skill","source-github","skill-mvvm-toolkit","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","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 (10,238 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.140Z","embedding":null,"createdAt":"2026-05-11T06:52:41.301Z","updatedAt":"2026-05-18T18:52:18.140Z","lastSeenAt":"2026-05-18T18:52:18.140Z","tsv":"'/communitytoolkit/dotnet':954 '/communitytoolkit/mvvm-samples':958 '/en-us/dotnet/communitytoolkit/mvvm/':943 '/en-us/windows/apps/tutorials/winui-mvvm-toolkit/intro':950 '1':680 '100':630 '2':628,695 '3':714 '4':741 '5':756 '8':42 '95':537 'alloc':456 'allocation-fre':455 'allow':251 'allowconcurrentexecut':247 'also':175,243,306 'alway':738 'analyz':110 'app':38,803,912 'appli':155 'area':677 'arg':358,363 'async':239,249,261,298,467,482,545,715,728 'asyncrelaycommand':123,218,513 'attribut':154,314,534,896 'author':29 'avail':368 'await':269,474,487,491 'base':16,552,555 'becom':730 'behind':882 'bool':505,862,869 'broadcast':207 'button':746 'c':592 'call':187,196,454 'cancel':546 'cancellationtoken':242,485 'candelet':855,870 'canexecut':224,230,496,838,853 'cansav':498,506,751,840,863 'canx':226 'captur':449 'case':539 'chang':190,212,351 'cheat':152 'class':17,71,329,425,553,556,621,683,812 'clearallerror':657 'code':319,881 'code-behind':880 'collid':703 'combin':604 'command':21,34,388,458,521,806 'communitytoolkit.mvvm':4,23,41 'communitytoolkit.mvvm.componentmodel':116 'communitytoolkit.mvvm.input':120 'companion':44,890,923,931 'compil':381,691 'compos':526 'composit':607 'concurr':547 'constructor':514 'contactviewmodel':330 'core':8,24 'cost':384 'cover':536 'csharp':114,326,339,389,422,459,615,808 'custom':660,666 'customvalid':663 'declar':141 'deep':905 'default':254,560,767 'deleteasync':858 'deletecommand':826 'demonstr':804 'depend':385,387 'di':59,877,936 'diagnost':783,917 'disabl':255,748 'dive':906 'downloadasync':484 'e.g':285,608 'elid':378 'email':640 'emailaddress':637 'emit':147 'enclos':135,686 'end':793,795 'end-to-end':792 'entri':651 'equalitycompar':766 'error':549,692 'error-surfac':548 'even':749 'everi':126,134,685 'except':264,735 'executiontask':266 'explicit':524 'expos':219 'extend':601 'extern':937 'extra':109 'field':68,160,174,186,193,204,275,288,697,765 'filenam':829,847,850,860,865,872 'fire':772 'firstnam':399,413 'flowexceptionstotaskschedul':259 'foreach':470 'forget':681,742 'form':83,574 'forward':279 'framework':99 'free':457 'full':313,674,782,875,909 'fullnam':393,403,412 'generat':10,102,146,151,157,244,283,318,706,720,805,895 'generated-cod':317 'get':433 'geterror':658 'github.com':953,957 'github.com/communitytoolkit/dotnet':952 'github.com/communitytoolkit/mvvm-samples':956 'haserror':647 'held':761 'hook':169,336,376 'http.getstreamasync':492 'iasyncrelaycommand':222,727 'imesseng':52,87,583,610,816 'implement':369 'includecancelcommand':237,479 'inherit':76,596 'inject':609 'inotesservic':814 'inotifydataerrorinfo':573 'inotifypropertychang':162,561,562 'input':82,576 'instanc':74,214,227,775 'instead':267,776 'integr':62 'invoc':253 'irelaycommand':221 'item':472,477 'items.add':476 'items.reset':464 'jsonignor':286 'keep':452 'lambda':447 'lastnam':409,414 'lazi':216 'lead':303 'learn.microsoft.com':942,949 'learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/':941 'learn.microsoft.com/en-us/windows/apps/tutorials/winui-mvvm-toolkit/intro':948 'lifetim':523 'list':180 'load':46,55 'loadasync':295,469 'loadcommand':296 'logger.loginformation':349 'm':291,712 'manual':511 'maxlength':629 'messag':584 'messeng':50,590,817,819,921,928 'messenger.send':844 'method':75,168,215,228,233,240,250,262,278,294,664,725 'microsoft.extensions.dependencyinjection':61,929 'mini':796 'minlength':627 'model':421 'mono':100 'must':139,518,688 'mutat':757,778 'mvvm':2,6,48,57,588,926,934,945 'mvvm-toolkit':1 'mvvm-toolkit-di':56,933 'mvvm-toolkit-messeng':47,587,925 'mvvmtk0008':148,693 'mvvmtk0042':149,694 'mvvmtk0xxx':785,916 'mycommand':184 'mycommand.notifycanexecutechanged':188 'n':441,443 'name':287,289,290,292,293,335,343,350,353,432,503,508,633,656,698,702,709,710,713 'nameof':171,183,225,392,395,402,405,497,822,825,832,839,854 'namespac':113 'need':374,572 'nest':138 'net':97,98 'net6.0':94 'netstandard2.0':92 'netstandard2.1':93 'new':209,845 'newvalu':365 'non':419,530 'non-observ':418 'non-trivi':529 'note':802,815,911 'notes-app':910 'notes.deleteasync':859 'notes.saveasync':849 'notesavedmessag':846 'noteviewmodel':813 'notif':771 'notifycanexecutechangedfor':14,182,394,404,743,807,821,824,831 'notifydataerrorinfo':15,191,625,635 'notifypropertychangedfor':13,170,391,401 'notifypropertychangedrecipi':202 'nuget':107 'observ':173,185,192,203,274,324,420 'observableobject':18,78,117,331,429,559,602 'observableproperti':11,65,118,130,158,332,340,390,400,624,634,699,764,820,830 'observablerecipi':20,84,206,577,599,818 'observableus':426 'observablevalid':19,80,195,569,597,613,623 'old':208 'oldvalu':364 'one':372 'onnamechang':346 'onto':281 'onxxxchang':164,165,337,338 'overload':366,564 'overview':940 'packag':88 'pane':801 'partial':70,142,167,328,344,620,682,690,811 'partial-method':166 'pascalcas':696 'pass':444 'pattern':54,322 'pitfal':679,788,918 'point':652 'privat':67,159,333,341,397,407,461,466,481,499,504,631,638,642,700,827,834,841,856,861,868 'properti':33,163,181,235,272,284,325,386,568,707 'propertychang':177 'propertynam':659 'pub/sub':53,922 'public':161,327,410,423,430,618,809 'queued/parallel':252 'quick':63 'rais':176 'reach':509 'recap':64 'receiv':582 'recip':551,901 'refer':111,315,760,889,897 'references/end-to-end-walkthrough.md':887,888,914,915 'references/relaycommand-cookbook.md':541,542,902,903 'references/source-generators.md':309,310,898,899 'references/troubleshooting.md':790,791,919,920 'references/validation.md':670,671,907,908 'refresh':463 'registrationviewmodel':622 'relayco':22 'relaycommand':12,72,121,122,132,213,217,223,236,246,258,277,460,465,478,495,512,641,718,733,837,852,900 'replac':773 'repo.saveasync':502 'requir':112,606,626,636 'rethrow':271 'return':648,724,739,754,768,848 'review':31 'rule':125,661 'run':257 'runtim':383 'sampl':320,876,955 'save':745 'saveasync':501,843 'savecommand':396,406,823,833 'seal':424,619,810 'see':308,540,585,669,789,886 'select':554 'send':580 'service.getitemsasync':475 'set':435,575 'setproperti':436,563 'setpropertyandnotifyoncomplet':565 'setter':201 'setup':89 'sheet':153 'ship':103 'simpl':323 'singl':357,595 'single-arg':356 'single-inherit':594 'skill':27,45,591,891,924,932 'skill-mvvm-toolkit' 'someattr':273,280 'sourc':9,101,150,532,894,938,951 'source-github' 'state':450 'static':446 'stay':747 'stream':490 'string':334,342,347,398,408,411,431,632,639,701,828,835 'string.isnullorempty':866 'string.isnullorwhitespace':507,864,871 'strip':301,307 'style':535 'subclass':668 'submit':644,649 'suffix':299 'support':662 'surfac':263,550,676 'sync':544,732 'system.componentmodel.dataannotations':617 'tabl':784 'target':91 'task':468,483,500,567,723,740,842,857 'test':885 'text':836,851,867 'though':750 'token':486,494 'toolkit':3,7,49,58,589,927,935,939,946 'top':678 'topic':892 'topic-agent-skills' 'topic-agents' 'topic-awesome' 'topic-custom-agents' 'topic-github-copilot' 'topic-hacktoberfest' 'topic-prompt-engineering' 'trim':415 'trivial':531 'true':238,248,260,480,755,769 'trysetproperti':653 'tutori':947 'two':362,800 'two-arg':361 'two-pan':799 'type':127,136,687 'u':440 'u.name':442 'unimpl':375 'unit':884 'univers':124 'unobserv':737 'url':493 'use':25,40,86,115,119,129,488,557,616,708 'user':427,428,439 'user.name':434,437 'valid':36,614,675,904 'validateallproperti':645 'validateproperti':197,654 'validationattribut':667 'valu':198,348,354,359,438,655 'var':471,489 'via':265 'view':879 'viewmodel':32,321 'vm':571,579 'void':345,462,643,716,729 'walkthrough':797,913 'winui':944 'wire':229,878,930 'without':143 'work':95 'would':752 'wrap':416,722 'x':43 'xaml':883 'xml':90 'xxxcancelcommand':245 'zero':382","prices":[{"id":"70654807-6743-43c7-956b-44dec94a115b","listingId":"be0cda3b-01f3-4713-9e34-397911c170b7","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.301Z"}],"sources":[{"listingId":"be0cda3b-01f3-4713-9e34-397911c170b7","source":"github","sourceId":"github/awesome-copilot/mvvm-toolkit","sourceUrl":"https://github.com/github/awesome-copilot/tree/main/skills/mvvm-toolkit","isPrimary":false,"firstSeenAt":"2026-05-11T06:52:41.301Z","lastSeenAt":"2026-05-18T18:52:18.140Z"}],"details":{"listingId":"be0cda3b-01f3-4713-9e34-397911c170b7","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"github","slug":"mvvm-toolkit","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":"3032cb4a5fd82befd63ca41c05a45ced9bffe79f","skill_md_path":"skills/mvvm-toolkit/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/github/awesome-copilot/tree/main/skills/mvvm-toolkit"},"layout":"multi","source":"github","category":"awesome-copilot","frontmatter":{"name":"mvvm-toolkit","description":"CommunityToolkit.Mvvm (the MVVM Toolkit) core: source generators ([ObservableProperty], [RelayCommand], [NotifyPropertyChangedFor], [NotifyCanExecuteChangedFor], [NotifyDataErrorInfo]), base classes (ObservableObject / ObservableValidator / ObservableRecipient), commands (RelayCommand / AsyncRelayCommand), and validation. Companion skills: mvvm-toolkit-messenger for pub/sub, mvvm-toolkit-di for Microsoft.Extensions.DependencyInjection wiring. Works across WPF, WinUI 3, MAUI, Uno, and Avalonia."},"skills_sh_url":"https://skills.sh/github/awesome-copilot/mvvm-toolkit"},"updatedAt":"2026-05-18T18:52:18.140Z"}}