Skip to content
Development
Skill

/mvvm

Implement the Model-View-ViewModel pattern in .NET applications with proper separation of concerns, data binding, commands, and testable ViewModels using MVVM Toolkit. USE FOR: implementing UI separation with Model-View-ViewModel; using MVVM Toolkit (CommunityToolkit.Mvvm) for

From plugin
dotnet-skills
499200 skills17 agents

Claude Code

$ npx -y skills add managedcode/dotnet-skills --skill mvvm --agent claude-code

SKILL.md

mvvm.SKILL.md
name: mvvm
description: "Implement the Model-View-ViewModel pattern in .NET applications with proper separation of concerns, data binding, commands, and testable ViewModels using MVVM Toolkit. USE FOR: implementing UI separation with Model-View-ViewModel; using MVVM Toolkit (CommunityToolkit.Mvvm) for ViewModels; designing testable UI architecture. DO NOT USE FOR: unrelated stacks; generic tasks that do not need this specific guidance. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made."
compatibility: "Applies to WPF, MAUI, WinUI, Uno Platform, Avalonia, and Blazor projects."

MVVM Pattern for .NET

Trigger On

  • implementing UI separation with Model-View-ViewModel
  • using MVVM Toolkit (CommunityToolkit.Mvvm) for ViewModels
  • designing testable UI architecture
  • handling commands, property changes, and messaging
  • choosing between MVVM frameworks

Documentation

  • [MVVM Toolkit Overview](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/)
  • [ObservableObject](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/observableobject)
  • [RelayCommand](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/relaycommand)
  • [Messenger](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger)
  • [Source Generators](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/overview)

References

See detailed examples in the `references/` folder:

  • [`patterns.md`](references/patterns.md) — ViewModel, command, navigation, and state patterns
  • [`anti-patterns.md`](references/anti-patterns.md) — Common mistakes and how to fix them

Core Concepts

| Component | Responsibility | Example | |-----------|---------------|---------| | **Model** | Business logic and data | `Product`, `Order`, `User` | | **View** | UI presentation (XAML/Razor) | `ProductPage.xaml` | | **ViewModel** | UI logic and state | `ProductViewModel` |

Workflow

1. **Keep Views dumb** — no business logic in code-behind 2. **Use data binding** — connect View to ViewModel properties 3. **Commands for actions** — handle user interactions via ICommand 4. **Inject dependencies** — services go into ViewModel constructors 5. **Test ViewModels** — they should be unit testable without UI

MVVM Toolkit Setup

<PackageReference Include="CommunityToolkit.Mvvm" Version="8.*" />

ViewModel Patterns

Basic ViewModel with Source Generators

public partial class ProductViewModel(IProductService productService) : ObservableObject
{
    [ObservableProperty]
    private string _name = string.Empty;

    [ObservableProperty]
    private decimal _price;

    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(SaveCommand))]
    private bool _isValid;

    [RelayCommand(CanExecute = nameof(CanSave))]
    private async Task SaveAsync()
    {
        await productService.SaveAsync(new Product { Name = Name, Price = Price });
    }

    private bool CanSave() => IsValid && !string.IsNullOrEmpty(Name);
}

Property Changed Notifications

public partial class OrderViewModel : ObservableObject
{
    [ObservableProperty]
    private int _quantity;

    [ObservableProperty]
    private decimal _unitPrice;

    // Computed property - manually notify
    public decimal Total => Quantity * UnitPrice;

    partial void OnQuantityChanged(int value)
    {
        OnPropertyChanged(nameof(Total));
    }

    partial void OnUnitPriceChanged(decimal value)
    {
        OnPropertyChanged(nameof(Total));
    }
}

Collection ViewModel

public partial class ProductListViewModel(IProductService productService) : ObservableObject
{
    [ObservableProperty]
    private ObservableCollection<ProductViewModel> _products = [];

    [ObservableProperty]
    private ProductViewModel? _selectedProduct;

    [ObservableProperty]
    private bool _isLoading;

    [RelayCommand]
    private async Task LoadProductsAsync()
    {
        IsLoading = true;
        try
        {
            var items = await productService.GetAllAsync();
            Products = new ObservableCollection<ProductViewModel>(
                items.Select(p => new ProductViewModel(productService)
                {
                    Name = p.Name,
                    Price = p.Price
                }));
        }
        finally
        {
            IsLoading = false;
        }
    }

    [RelayCommand]
    private void DeleteProduct(ProductViewModel product)
    {
        Products.Remove(product);
    }
}

Commands

Async Commands with Cancellation

public partial class SearchViewModel : ObservableObject
{
    [ObservableProperty]
    private string _searchText = string.Empty;

    [RelayCommand(IncludeCancelCommand = true)]
    private async Task SearchAsync(CancellationToken token)
    {
        await Task.Delay(500, token); // Debounce
        // Search logic with cancellation support
    }
}

Command with Parameter

public partial class NavigationViewModel : ObservableObject
{
    [RelayCommand]
    private void NavigateTo(string page)
    {
        // Navigate to page
    }

    [RelayCommand]
    private async Task OpenItemAsync(int itemId)
    {
        // Load and open item
    }
}

Messenger Pattern

Sending Messages

// Define message
public record ProductSelectedMessage(Product Product);

// Send from one ViewModel
WeakReferenceMessenger.Default.Send(new ProductSelectedMessage(selectedProduct));

Receiving Messages

public partial class ProductDetailViewModel : ObservableRecipient
{
    public ProductDetailViewModel()
    {
        IsActive = true; // Enable message reception
    }

    protected override void OnActivated()
    {
        Messenger.Register<ProductDetailViewModel, ProductSelectedMessage>(
            this, (r, m) => r.LoadProduct(m.Product));
    }

    private void LoadP
Read more
@managedcode
Ships withdotnet-skills

Stop explaining .NET to your AI. Start building. We've all been there: asking Claude to use Entity Framework, only to get EF6 patterns in a .NET 8 project. Explaining to Copilot that Blazor Server and Blazor WebAssembly aren't the same thing.

499200 skills17 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
499
Star history of managedcode/dotnet-skills on GitHub, a chart by star-history.com

Other skills on dotnet-skills.

aspnet-core

aspnet-core

Build, debug, modernize, or review ASP.NET Core applications with correct hosting, middleware, security, configuration, logging,…

aspire

aspire

Build, upgrade, and operate Aspire 13.5.x C# or TypeScript application hosts with the current CLI, AppHost, ServiceDefaults,…

avalonia

avalonia

Build and debug Avalonia 11/12 .NET applications with XAML, compiled bindings, MVVM, custom controls, desktop lifetimes, and…

azure-functions

azure-functions

Build, review, or migrate Azure Functions in .NET with correct execution model, isolated worker setup, bindings, DI, and Durable…

blazor

blazor

Build and review Blazor applications across server, WebAssembly, web app, and hybrid scenarios with correct component design,…

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills11d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc13d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react2d252k
@facebook@facebookInstall Skill

Questions about mvvm.

Which coding agents can use mvvm?
mvvm installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.