# MVVM Rules / Quy Tắc MVVM Hướng dẫn chi tiết về Model-View-ViewModel pattern với CommunityToolkit.Mvvm. ## Core Principles / Nguyên Tắc Cốt Lõi 1. **Separation of Concerns**: View chỉ xử lý UI, ViewModel xử lý logic 2. **Testability**: ViewModel có thể unit test độc lập 3. **No Code-behind Logic**: Không viết business logic trong `.xaml.cs` --- ## CommunityToolkit.Mvvm Setup ```xml ``` ```csharp // MauiProgram.cs builder.UseMauiCommunityToolkit(); ``` --- ## ViewModel Patterns ### 1. ObservableProperty (Auto-generate INotifyPropertyChanged) ```csharp // ✅ ĐÚNG: Source Generator pattern public partial class ProductViewModel : ObservableObject { [ObservableProperty] private string _name = string.Empty; [ObservableProperty] private decimal _price; [ObservableProperty] [NotifyPropertyChangedFor(nameof(FullDescription))] private string _category = string.Empty; // Computed property public string FullDescription => $"{Name} - {Category}"; } ``` ```csharp // ❌ SAI: Manual implementation (verbose) public class ProductViewModel : INotifyPropertyChanged { private string _name; public string Name { get => _name; set => SetProperty(ref _name, value); } // ... repetitive code } ``` ### 2. RelayCommand (Auto-generate ICommand) ```csharp public partial class ProductListViewModel : ObservableObject { [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SaveCommand))] private bool _hasChanges; // Async command với cancellation [RelayCommand] private async Task LoadDataAsync(CancellationToken token) { var products = await _service.GetAllAsync(token); Products = new ObservableCollection(products); } // Command with CanExecute [RelayCommand(CanExecute = nameof(CanSave))] private async Task SaveAsync() { await _service.SaveAsync(CurrentProduct); HasChanges = false; } private bool CanSave() => HasChanges && CurrentProduct != null; // Command with parameter [RelayCommand] private void SelectProduct(Product product) { SelectedProduct = product; } } ``` ### 3. WeakReferenceMessenger (Event Aggregation) ```csharp // Send message WeakReferenceMessenger.Default.Send(new ProductUpdatedMessage(product)); // Receive message (register in constructor) public partial class ProductListViewModel : ObservableObject, IRecipient { public ProductListViewModel() { WeakReferenceMessenger.Default.Register(this); } public void Receive(ProductUpdatedMessage message) { // Handle product update var product = Products.FirstOrDefault(p => p.Id == message.Product.Id); if (product != null) { var index = Products.IndexOf(product); Products[index] = message.Product; } } } // Message definition public record ProductUpdatedMessage(Product Product); ``` --- ## View-ViewModel Binding ### Constructor Injection Pattern ```csharp // View (ProductListPage.xaml.cs) public partial class ProductListPage : ContentPage { public ProductListPage(ProductListViewModel viewModel) { InitializeComponent(); BindingContext = viewModel; } protected override async void OnNavigatedTo(NavigatedToEventArgs args) { base.OnNavigatedTo(args); // Trigger data loading if (BindingContext is ProductListViewModel vm) { await vm.LoadDataCommand.ExecuteAsync(null); } } } ``` ### XAML Binding ```xml