# End-to-end walkthrough: WinUI 3 Notes app A minimal Notes app demonstrating the full MVVM Toolkit story: `ObservableObject`/`ObservableRecipient`, `[ObservableProperty]`, `[RelayCommand]`, `[NotifyCanExecuteChangedFor]`, `WeakReferenceMessenger`, and `Microsoft.Extensions.DependencyInjection`. This walkthrough mirrors the official tutorial at . > The same pattern works on WPF, MAUI, Uno, and Avalonia — only the > XAML, navigation, and `App` bootstrap differ. --- ## Project layout ``` MyApp/ ← WinUI 3 app project App.xaml.cs Views/ AllNotesPage.xaml NotePage.xaml MyApp.Shared/ ← .NET class library — ViewModels + services ViewModels/ AllNotesViewModel.cs NoteViewModel.cs Services/ INotesService.cs FileSystemNotesService.cs Messages/ NoteSavedMessage.cs NoteDeletedMessage.cs MyApp.Tests/ ← xUnit / MSTest project — VM unit tests ``` Putting ViewModels in a separate library is the recommended pattern: the library has no UI dependencies, so VMs are unit-testable in isolation. --- ## 1. The model Plain POCO — no toolkit dependencies. ```csharp public sealed record NoteSummary(string Filename, string Preview, DateTime LastModified); ``` --- ## 2. The service ```csharp public interface INotesService { Task> GetAllAsync(); Task LoadAsync(string filename); Task SaveAsync(string filename, string text); Task DeleteAsync(string filename); } public sealed class FileSystemNotesService(string rootFolder) : INotesService { public async Task> GetAllAsync() { var files = Directory.GetFiles(rootFolder, "*.txt"); var summaries = new List(files.Length); foreach (var f in files) { var text = await File.ReadAllTextAsync(f); summaries.Add(new NoteSummary( Path.GetFileName(f), text.Length > 60 ? text[..60] : text, File.GetLastWriteTime(f))); } return summaries; } public Task LoadAsync(string filename) => File.ReadAllTextAsync(Path.Combine(rootFolder, filename)); public Task SaveAsync(string filename, string text) => File.WriteAllTextAsync(Path.Combine(rootFolder, filename), text); public Task DeleteAsync(string filename) { File.Delete(Path.Combine(rootFolder, filename)); return Task.CompletedTask; } } ``` --- ## 3. The messages ```csharp public sealed record NoteSavedMessage(string Filename); public sealed record NoteDeletedMessage(string Filename); ``` --- ## 4. The list view model ```csharp using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; public sealed partial class AllNotesViewModel : ObservableRecipient, IRecipient, IRecipient { private readonly INotesService notes; public AllNotesViewModel(INotesService notes, IMessenger messenger) : base(messenger) { this.notes = notes; IsActive = true; // start listening for messages } public ObservableCollection Notes { get; } = new(); [RelayCommand] private async Task LoadAsync() { Notes.Clear(); foreach (var n in await notes.GetAllAsync()) Notes.Add(n); } public void Receive(NoteSavedMessage message) => _ = LoadAsync(); public void Receive(NoteDeletedMessage message) => _ = LoadAsync(); } ``` `ObservableRecipient`'s `OnActivated` (called when `IsActive` becomes `true`) wires up the `IRecipient` handlers automatically. --- ## 5. The editor view model ```csharp public sealed partial class NoteViewModel : ObservableRecipient { private readonly INotesService notes; public NoteViewModel(INotesService notes, IMessenger messenger) : base(messenger) { this.notes = notes; } [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SaveCommand))] [NotifyCanExecuteChangedFor(nameof(DeleteCommand))] private string? filename; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SaveCommand))] private string? text; [RelayCommand] private async Task LoadAsync(string filename) { Filename = filename; Text = await notes.LoadAsync(filename); } [RelayCommand(CanExecute = nameof(CanSave))] private async Task SaveAsync() { await notes.SaveAsync(Filename!, Text!); Messenger.Send(new NoteSavedMessage(Filename!)); } [RelayCommand(CanExecute = nameof(CanDelete))] private async Task DeleteAsync() { await notes.DeleteAsync(Filename!); Messenger.Send(new NoteDeletedMessage(Filename!)); Filename = null; Text = null; } private bool CanSave() => !string.IsNullOrWhiteSpace(Filename) && !string.IsNullOrEmpty(Text); private bool CanDelete() => !string.IsNullOrWhiteSpace(Filename); } ``` --- ## 6. The composition root (`App.xaml.cs`) ```csharp using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using CommunityToolkit.Mvvm.Messaging; public partial class App : Application { public IHost Host { get; } public App() { InitializeComponent(); var notesRoot = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "MyApp", "notes"); Directory.CreateDirectory(notesRoot); Host = Microsoft.Extensions.Hosting.Host .CreateDefaultBuilder() .ConfigureServices((_, services) => { services.AddSingleton(_ => new FileSystemNotesService(notesRoot)); services.AddSingleton(WeakReferenceMessenger.Default); services.AddSingleton(); services.AddTransient(); }) .Build(); } public static T GetService() where T : class => ((App)Current).Host.Services.GetRequiredService(); } ``` --- ## 7. Wire up the views `AllNotesPage.xaml.cs`: ```csharp public sealed partial class AllNotesPage : Page { public AllNotesViewModel ViewModel { get; } = App.GetService(); public AllNotesPage() { InitializeComponent(); } protected override async void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); await ViewModel.LoadCommand.ExecuteAsync(null); } } ``` `AllNotesPage.xaml`: ```xml ``` `NotePage.xaml.cs`: ```csharp public sealed partial class NotePage : Page { public NoteViewModel ViewModel { get; } = App.GetService(); public NotePage() { InitializeComponent(); } protected override async void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); if (e.Parameter is string filename) await ViewModel.LoadCommand.ExecuteAsync(filename); ViewModel.IsActive = true; } protected override void OnNavigatedFrom(NavigationEventArgs e) { ViewModel.IsActive = false; base.OnNavigatedFrom(e); } } ``` `NotePage.xaml`: ```xml