api-versioning
API versioning strategies for ASP.NET Core. Covers Asp.Versioning library, URL segment, header, and query string strategies,…
MVVM for .NET desktop apps (WPF, Avalonia, WinForms, MAUI) with CommunityToolkit.Mvvm 8.4+ source generators: [ObservableProperty] partial properties, [RelayCommand] async commands, Generic Host + DI, UI-thread dispatching, navigation, messaging, and testable view models. Load
Claude Code
$ npx -y skills add codewithmukesh/dotnet-claude-kit --skill desktop-mvvm --agent claude-code
Codex
$ npx skills add codewithmukesh/dotnet-claude-kit --skill desktop-mvvm -a codex -y
Cursor
$ npx skills add codewithmukesh/dotnet-claude-kit --skill desktop-mvvm -a cursor -y
Gemini CLI
$ npx skills add codewithmukesh/dotnet-claude-kit --skill desktop-mvvm -a gemini-cli -y
GitHub Copilot
$ npx skills add codewithmukesh/dotnet-claude-kit --skill desktop-mvvm -a github-copilot -y
OpenCode
$ npx skills add codewithmukesh/dotnet-claude-kit --skill desktop-mvvm -a opencode -y
Windsurf
$ npx skills add codewithmukesh/dotnet-claude-kit --skill desktop-mvvm -a windsurf -y
name: desktop-mvvm description: > MVVM for .NET desktop apps (WPF, Avalonia, WinForms, MAUI) with CommunityToolkit.Mvvm 8.4+ source generators: [ObservableProperty] partial properties, [RelayCommand] async commands, Generic Host + DI, UI-thread dispatching, navigation, messaging, and testable view models. Load when the user mentions "MVVM", "ViewModel", "WPF", "desktop app", "ObservableProperty", "RelayCommand", "ICommand", "INotifyPropertyChanged", "Dispatcher", or "UI thread".
1. **View models know nothing about the UI framework** — No `Window`, `MessageBox`, `Dispatcher`, or `Visibility` in a view model. That keeps them unit-testable without a UI thread and portable between WPF, Avalonia, and MAUI. 2. **Source generators over hand-written `INotifyPropertyChanged`** — CommunityToolkit.Mvvm (8.4.x, verified) generates the property, change notification, and command boilerplate at compile time. There's no reflection, so it stays trimming- and AOT-friendly. 3. **Use partial properties, not fields** — Since 8.4, `[ObservableProperty]` goes on `partial` properties. From 8.4.1 that works on C# 14 with no `LangVersion=preview`. You get real properties that you can put attributes on and debug, instead of fields with magic names. 4. **Async commands, never `async void`** — `[RelayCommand]` on a `Task`-returning method generates an `IAsyncRelayCommand`. It disables itself while running, surfaces exceptions, and supports cancellation. 5. **Generic Host is the composition root** — DI, configuration, logging, and `IHttpClientFactory` work the same as in ASP.NET Core. Views and view models are resolved from the container, never `new`-ed in code-behind.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public sealed partial class OrdersViewModel(IOrderService orders, TimeProvider clock) : ObservableObject
{
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
public partial string? CustomerName { get; set; }
[ObservableProperty]
public partial OrderRow? SelectedOrder { get; set; }
public ObservableCollection<OrderRow> Orders { get; } = [];
// Generates LoadCommand (IAsyncRelayCommand) + LoadCancelCommand
[RelayCommand(IncludeCancelCommand = true)]
private async Task LoadAsync(CancellationToken ct)
{
Orders.Clear();
await foreach (var order in orders.StreamAsync(ct))
Orders.Add(order); // continuation resumes on the UI thread
}
[RelayCommand(CanExecute = nameof(CanSave))]
private Task SaveAsync(CancellationToken ct) =>
orders.CreateAsync(CustomerName!, clock.GetUtcNow(), ct);
private bool CanSave() => !string.IsNullOrWhiteSpace(CustomerName);
partial void OnSelectedOrderChanged(OrderRow? value) =>
CustomerName = value?.Customer; // generated hook, no setter boilerplate
}**Why:** the class must be `partial` so the generator can add members. `Async` is stripped from command names (`LoadAsync` → `LoadCommand`). The command is disabled while it runs (unless `AllowConcurrentExecutions = true`), so double-clicks can't start two loads. Bind a busy indicator to `LoadCommand.IsRunning` instead of keeping a hand-rolled `IsBusy` flag.
Remove `StartupUri` from `App.xaml`, then own startup in `App.xaml.cs`:
public sealed partial class App : Application
{
private readonly IHost host = CreateHost();
private static IHost CreateHost()
{
var builder = Host.CreateApplicationBuilder(new HostApplicationBuilderSettings
{
ContentRootPath = AppContext.BaseDirectory // appsettings.json next to the exe, not the cwd
});
builder.Services.AddSingleton(TimeProvider.System);
builder.Services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default);
builder.Services.AddHttpClient<IOrderService, OrderService>();
builder.Services.AddTransient<OrdersViewModel>();
builder.Services.AddSingleton<MainWindow>();
return builder.Build();
}
protected override void OnStartup(StartupEventArgs e)
{
host.Start(); // starts IHostedService background work, if any
host.Services.GetRequiredService<MainWindow>().Show();
base.OnStartup(e);
}
protected override void OnExit(ExitEventArgs e)
{
// Process exit is the one place sync-over-async is acceptable (like Program.cs)
host.StopAsync(TimeSpan.FromSeconds(5)).GetAwaiter().GetResult();
host.Dispose();
base.OnExit(e);
}
}
public sealed partial class MainWindow : Window
{
public MainWindow(OrdersViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}**Why:** one composition root shared with your backend knowledge. Constructor injection into windows keeps code-behind to `InitializeComponent` + `DataContext`. Setting the content root to `AppContext.BaseDirectory` stops a shortcut's "Start in" folder from changing which config loads. Avalonia wiring lives in the `avalonia` skill.
`await` started on the UI thread resumes on the UI thread, so most code needs **no** dispatching. Dispatch only for callbacks that arrive on background threads (timers, `FileSystemWatcher`, sockets, message buses).
// 1. Progress<T> captures the UI SynchronizationContext when constructed on the UI thread
[RelayCommand]
private async Task ExportAsync(CancellationToken ct)
{
var progress = new Progress<int>(percent => ExportProgress = percent);
await Task.Run(() => exporter.Export(Orders.ToList(), progress, ct), ct); // CPU work off the UI thread
}
// 2. A tiny abstraction for background-origin events, implemented per framework
public interface IUiDispatcher
{
Task InvokeAsync(Action actionAPI versioning strategies for ASP.NET Core. Covers Asp.Versioning library, URL segment, header, and query string strategies,…
Architecture conformance check: verifies an existing codebase against its declared architecture (VSA, Clean Architecture, DDD,…
Architecture selection advisor for .NET applications. Asks structured questions about domain complexity, team size, system…
.NET Aspire for cloud-native orchestration. Covers AppHost configuration, service defaults, resource configuration, service…
Authentication and authorization for ASP.NET Core. Covers JWT bearer tokens, OpenID Connect, ASP.NET Identity, authorization…
Avalonia UI 12 for cross-platform .NET desktop apps: compiled bindings (x:DataType, on by default in 12),…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.