CommunityToolkit.Mvvm (MVVM Toolkit) coding conventions for ViewModels, commands, messaging, validation, and DI across WPF, WinUI 3, .NET MAUI, Uno Platform, and Avalonia. Use when writing or reviewing mvvm toolkit code and need idiomatic conventions and best practices.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill ghcopilot-instr-mvvm-toolkit --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Ghcopilot Instr Mvvm Toolkit?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-ghcopilot-instr-mvvm-toolkit)More formats (shields.io, HTML) on the badges page.
---
name: ghcopilot-instr-mvvm-toolkit
description: CommunityToolkit.Mvvm (MVVM Toolkit) coding conventions for ViewModels, commands, messaging, validation, and DI across WPF, WinUI 3, .NET MAUI, Uno Platform, and Avalonia. Use when writing or reviewing mvvm toolkit code and need idiomatic conventions and best practices.
license: CC-BY-NC-SA-4.0
metadata:
version: "1.0.0"
tags: [copilot-instruction, community, mvvm-toolkit]
applyTo: "**/*.cs, **/*.xaml, **/*.csproj"
source: "awesome-copilot"
---
# CommunityToolkit.Mvvm (MVVM Toolkit)
These rules apply whenever a project references `CommunityToolkit.Mvvm`.
For deep reference and end-to-end examples, load the `mvvm-toolkit` skill.
## Package & language
- Reference `CommunityToolkit.Mvvm` 8.x (or newer) in `.csproj`. Do not
install the legacy `Microsoft.Toolkit.Mvvm` (7.x) for new projects.
- C# `LangVersion` must support source generators (default in modern SDKs).
## ViewModel base class
- Inherit ViewModels from `ObservableObject` by default.
- Use `ObservableValidator` only when the ViewModel needs
`INotifyDataErrorInfo` (forms, settings, input validation).
- Use `ObservableRecipient` only when the ViewModel sends or receives
`IMessenger` messages.
- Never hand-implement `INotifyPropertyChanged` when one of the toolkit
base classes can be used. If the type cannot inherit from a toolkit base
(e.g., a custom control), apply the class-level `[ObservableObject]` or
`[INotifyPropertyChanged]` attribute instead.
## Properties
- Declare every type that uses `[ObservableProperty]` as `partial` (and
every enclosing type, if nested).
- Apply `[ObservableProperty]` to private fields named `name`, `_name`, or
`m_name` — never PascalCase. Let the generator emit the public property.
- Do not write manual `SetProperty(ref field, value)` boilerplate when the
field qualifies for `[ObservableProperty]`.
- Use `[NotifyPropertyChangedFor(nameof(Derived))]` to raise change
notifications for derived/computed properties.
- Use `[NotifyCanExecuteChangedFor(nameof(XxxCommand))]` so commands
re-evaluate `CanExecute` when their inputs change.
- Implement `OnXxxChanging` / `OnXxxChanged` partial-method hooks for
side-effects on property changes — do not subscribe to your own
`PropertyChanged` event.
- Use `[property: SomeAttribute]` to forward an attribute (e.g.,
`[JsonIgnore]`, `[JsonPropertyName(...)]`) onto the generated property.
## Commands
- Use `[RelayCommand]` on instance methods over manually constructed
`RelayCommand` / `AsyncRelayCommand` instances.
- `[RelayCommand]` methods must return `void` or `Task` (or `Task<T>`).
Never use `async void` — exceptions become unobserved.
- For cancellable async work, declare a `CancellationToken` parameter and
optionally set `IncludeCancelCommand = true` to expose a paired
`XxxCancelCommand`.
- Use `CanExecute = nameof(...)` plus `[NotifyCanExecuteChangedFor]` on the
inputs to keep button enable/disable state in sync.
- Default `AllowConcurrentExecutions` to `false` (the default). Only set
`true` when overlapping invocations are explicitly safe and intended.
- Default error policy is await-and-rethrow. Only set
`FlowExceptionsToTaskScheduler = true` when the UI binds to
`ExecutionTask` to render error states.
## Messaging
- Default to `WeakReferenceMessenger.Default`. Only switch to
`StrongReferenceMessenger.Default` when profiling shows the messenger is
hot, and document the lifetime guarantees.
- Register handlers with the `(recipient, message)` lambda form using the
`static` modifier — never capture `this` in the lambda.
- Prefer `IRecipient<TMessage>` interfaces on `ObservableRecipient`
ViewModels so `RegisterAll(this)` wires everything automatically when
`IsActive = true`.
- Set `IsActive = true` on activation (e.g., `OnNavigatedTo`) and
`IsActive = false` on deactivation (e.g., `OnNavigatedFrom`).
- Inheritance is not considered when delivering messages — register each
concrete message type explicitly.
- Use channel tokens (the `int` / `string` / `Guid` overloads) to scope
messages to a sub-system or window when more than one consumer would
otherwise collide.
## Dependency injection
- Use `Microsoft.Extensions.DependencyInjection` for service and ViewModel
registration. Prefer the .NET Generic Host
(`Host.CreateDefaultBuilder()`) so configuration, logging, and scope
validation are wired automatically.
- Register services and ViewModels in the composition root (typically
`App.xaml.cs`). Resolve the page's root ViewModel from DI in the page
constructor or via the navigation framework.
- Inject services and child ViewModels through constructors. Do not call
`Ioc.Default.GetService<T>()` from inside ViewModels, services, or any
type the DI container can construct.
- Lifetimes:
- `AddSingleton<T>()` — shell/main-window VMs, settings, file/HTTP
services, the shared `IMessenger`.
- `AddTransient<T>()` — per-page or per-document VMs.
- `AddScoped<T>()` — only with explicit `IServiceScope` usage; rarely
needed in client apps.
- Register `IMessenger` once
(`services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default)`)
and inject it via `ObservableRecipient(messenger)` constructors.
## Validation
- Use `ObservableValidator` plus `[NotifyDataErrorInfo]` and DataAnnotation
attributes (`[Required]`, `[Range]`, `[EmailAddress]`, `[MinLength]`,
`[MaxLength]`, `[CustomValidation]`).
- Call `ValidateAllProperties()` before submitting a form; check
`HasErrors` and bail out if `true`.
- Reset error state with `ClearAllErrors()` after a successful submit or
when resetting a form.
- For cross-property rules, call `ValidateProperty(value, nameof(Other))`
from the changed property's `OnXxxChanged` hook.
## XAML
- For WinUI 3 / UWP, prefer `{x:Bind}` (compiled bindings) over
`{Binding}`. Set `Mode=OneWay` or `Mode=TwoWay` explicitly — `{x:Bind}`
defaults to `OneTime`.
- Bind `Command="{x:Bind ViewModel.SaveCommand}"` directly to the
generated command property.
- Bind async-command status (`IsRunning`, `ExecutionTask.Status`,
`ExecutionTask.Exception`) to surface progress/errors instead of
blocking the UI thread.
## Things to avoid
- `[ObservableProperty] private string Name;` — PascalCase field collides
with the generated property; use lowerCamel.
- Manual `RaisePropertyChanged(nameof(X))` calls alongside
`[ObservableProperty]` — produces duplicate notifications.
- `Ioc.Default.GetService<T>()` from inside a ViewModel constructor —
hides dependencies, breaks unit tests.
- `StrongReferenceMessenger` without `OnDeactivated` / `UnregisterAll` —
pins recipients and leaks them.
- Capturing `this` in messenger lambdas — closure allocation and
lifetime confusion. Always use `(r, m) => r.OnX(m)` with `static`.
- `async void` on `[RelayCommand]` methods — return `Task` instead.
- Mutating the same reference held by an `[ObservableProperty]` field —
the equality comparer returns `true` and no change notification fires.
Replace the instance instead.
- Inheriting from both `ObservableValidator` and `ObservableRecipient` —
not possible; use composition (inject `IMessenger` or implement
validation manually).
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!