* WinUI plugin enhancements and mvvm toolkit skill * Split mvvm-toolkit skill for slimming
7.0 KiB
Troubleshooting
Common errors, diagnostics, and gotchas with CommunityToolkit.Mvvm 8.x.
Source-generator diagnostics (MVVMTK0xxx)
The generators emit numbered diagnostics. The most common ones:
| Code | Meaning | Fix |
|---|---|---|
MVVMTK0008 |
The containing type (or an enclosing type) is not partial |
Add partial to the class declaration and every enclosing type |
MVVMTK0016 |
[NotifyCanExecuteChangedFor] target is not an accessible IRelayCommand property |
Make sure the target is a [RelayCommand]-generated command (or a manually declared IRelayCommand property) on the same type |
MVVMTK0017 |
[NotifyDataErrorInfo] used outside ObservableValidator |
Inherit from ObservableValidator or remove the attribute |
MVVMTK0018 |
[NotifyPropertyChangedRecipients] used outside ObservableRecipient |
Inherit from ObservableRecipient or remove the attribute |
MVVMTK0030 |
[ObservableProperty] used in a type that does not implement INotifyPropertyChanged (and the class-level [INotifyPropertyChanged] / [ObservableObject] attributes are also missing) |
Inherit from ObservableObject or apply [INotifyPropertyChanged] / [ObservableObject] to the type |
MVVMTK0042 |
The [ObservableProperty] field belongs to a generic type without proper partial declarations |
Same fix as MVVMTK0008 (add partial) |
Search the full table at: https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/errors/
"Property name collides with field name"
'SampleViewModel' already contains a definition for 'Name'
You named the field with PascalCase:
[ObservableProperty]
private string Name; // ❌ collides with generated property
Use lowerCamel (or prefixed) instead:
[ObservableProperty]
private string? name; // ✅ generates Name
"Setter never raises PropertyChanged"
Possible causes:
- Same reference assigned. The generator uses
EqualityComparer<T>.Default.Equalsto detect changes. For reference types where you mutated the same instance, the comparer returnstrueand notification is skipped. Replace the instance instead of mutating. - Property set to identical value. Same value → no notification by design.
- Custom comparer needed. For value types where default equality is
wrong, write the property by hand and call
SetProperty(ref field, value, comparer).
"ContentDialog throws InvalidOperationException" (WinUI 3)
Not a toolkit issue, but commonly hit from [RelayCommand] async methods.
Set XamlRoot before calling ShowAsync(). See the
winui3-migration-guide skill for details.
Async [RelayCommand] swallows exceptions
Default behavior: the wrapped task is awaited and the exception is
rethrown on the synchronization context. If your method is async void,
the generator wraps it as a sync RelayCommand and exceptions become
unobserved. Always return Task from [RelayCommand] methods.
If the UI binds to ExecutionTask.Exception to render errors, opt into
FlowExceptionsToTaskScheduler = true:
[RelayCommand(FlowExceptionsToTaskScheduler = true)]
private async Task LoadAsync(CancellationToken token) { /* ... */ }
Cancellation appears to do nothing
- Ensure the wrapped method declares a
CancellationTokenparameter. - Pass the token down to the awaited APIs (
HttpClient.GetAsync(url, token),Task.Delay(ms, token), etc.). - Catch
OperationCanceledExceptionso the UI doesn't see an error.
Messenger handler never fires
Checklist:
- The recipient is registered for the exact message type, not a base type. Inheritance is not considered.
- The same
IMessengerinstance is used to send and register (WeakReferenceMessenger.Defaultvs an injected per-window messenger). - The token (channel) matches between sender and receiver.
- With
WeakReferenceMessenger, the recipient might already have been garbage-collected. Hold a strong reference somewhere (typically the DI container does this for singleton VMs). - With
ObservableRecipient,IsActivemust betrue—OnActivatedis what registers theIRecipient<T>handlers.
OnActivated never runs
ObservableRecipient.OnActivated is invoked when IsActive flips from
false to true. If you never set IsActive = true, no handlers register.
Common pattern:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
ViewModel.IsActive = true;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
ViewModel.IsActive = false;
}
Memory leak with StrongReferenceMessenger
Strong-ref recipients are pinned until you call Unregister. Either:
- Inherit from
ObservableRecipient(auto-unregisters inOnDeactivated). - Switch to
WeakReferenceMessenger.Default. - Call
messenger.UnregisterAll(this)in your dispose / tear-down path.
"Cannot inherit from ObservableValidator and ObservableRecipient"
C# single inheritance — pick one. If you need both:
- Inherit from
ObservableRecipient(orObservableValidator). - Inject
IMessenger(or implement validation) on the side via composition.
Or use the class-level [INotifyPropertyChanged] / [ObservableObject]
attribute on a custom base type that wraps both pieces.
DI container can't construct ViewModel
Symptom: InvalidOperationException mentioning "Unable to resolve service
for type 'X' while attempting to activate 'MyViewModel'".
Causes:
- Constructor parameter type wasn't registered. Add
services.AddX(...). - Multiple ambiguous constructors — the container picks the longest one whose dependencies are all registered. If two constructors qualify, an exception is thrown. Mark one as the canonical constructor or remove the ambiguity.
- Scoped service injected into a singleton (in dev mode with scope
validation). Either change the lifetime or inject
IServiceScopeFactoryand resolve from a scope.
XAML cannot resolve namespace
The type 'local:ContactViewModel' was not found.
XAML namespace mappings need the assembly to be referenced and the namespace to match. If the VM lives in a class library, the mapping needs the assembly name:
xmlns:vm="using:MyApp.Shared.ViewModels;assembly=MyApp.Shared"
(WPF syntax differs slightly: xmlns:vm="clr-namespace:...;assembly=...".)
"Design-time data shows nothing"
Design-time XAML editors instantiate the page without your DI container. Either:
- Provide a parameterless constructor that bootstraps a design-time VM.
- Use
d:DataContext="{d:DesignInstance Type=vm:ContactViewModel, IsDesignTimeCreatable=True}". - Use a separate design-time view model class with hard-coded sample data.
More
- All
MVVMTK0xxxerrors: https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/errors/ - Source: https://github.com/CommunityToolkit/dotnet
- Sample app: https://aka.ms/mvvmtoolkit/samples