# 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
<!-- .csproj -->
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
<PackageReference Include="CommunityToolkit.Maui" Version="9.1.0" />
```

```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<Product>(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<ProductUpdatedMessage>
{
    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
<ContentPage x:DataType="vm:ProductListViewModel">
    
    <!-- Property Binding -->
    <Label Text="{Binding Title}" />
    
    <!-- Command Binding -->
    <Button Text="Save" Command="{Binding SaveCommand}" />
    
    <!-- Command with Parameter -->
    <CollectionView ItemsSource="{Binding Products}">
        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="models:Product">
                <Grid>
                    <Grid.GestureRecognizers>
                        <TapGestureRecognizer 
                            Command="{Binding Source={RelativeSource AncestorType={x:Type vm:ProductListViewModel}}, Path=SelectProductCommand}"
                            CommandParameter="{Binding .}" />
                    </Grid.GestureRecognizers>
                    <Label Text="{Binding Name}" />
                </Grid>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
    
    <!-- IsRefreshing Binding -->
    <RefreshView IsRefreshing="{Binding IsLoading}"
                 Command="{Binding RefreshCommand}">
        <!-- Content -->
    </RefreshView>
</ContentPage>
```

---

## Validation Pattern

```csharp
public partial class ProductFormViewModel : ObservableValidator
{
    [ObservableProperty]
    [NotifyDataErrorInfo]
    [Required(ErrorMessage = "Name is required")]
    [MinLength(3, ErrorMessage = "Name must be at least 3 characters")]
    private string _name = string.Empty;
    
    [ObservableProperty]
    [NotifyDataErrorInfo]
    [Range(0.01, 999999, ErrorMessage = "Price must be positive")]
    private decimal _price;
    
    [RelayCommand]
    private async Task SubmitAsync()
    {
        ValidateAllProperties();
        
        if (HasErrors)
        {
            var errors = GetErrors();
            // Handle validation errors
            return;
        }
        
        await _service.CreateProductAsync(Name, Price);
    }
}
```

---

## Common Mistakes / Lỗi Thường Gặp

### 1. Logic in Code-behind

```csharp
// ❌ SAI
public partial class ProductPage : ContentPage
{
    private async void OnSaveClicked(object sender, EventArgs e)
    {
        var product = new Product { Name = NameEntry.Text };
        await _service.SaveAsync(product);
    }
}

// ✅ ĐÚNG: Logic trong ViewModel
// ViewModel
[RelayCommand]
private async Task SaveAsync() => await _service.SaveAsync(CurrentProduct);

// XAML
<Button Text="Save" Command="{Binding SaveCommand}" />
```

### 2. Missing [ObservableProperty] partial keyword

```csharp
// ❌ SAI: Missing partial
public class MyViewModel : ObservableObject
{
    [ObservableProperty]
    private string _name; // Won't generate property!
}

// ✅ ĐÚNG
public partial class MyViewModel : ObservableObject
{
    [ObservableProperty]
    private string _name;
}
```

### 3. Direct Collection Modification

```csharp
// ❌ SAI: UI won't update
Products.ToList().ForEach(p => p.IsSelected = false);

// ✅ ĐÚNG: Use ObservableCollection methods
foreach (var product in Products)
{
    product.IsSelected = false;
}
OnPropertyChanged(nameof(Products));
```

---

## Resources

- [CommunityToolkit.Mvvm Docs](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/)
- [Shell Navigation](./shell-nav.md)
- [DI Setup](./di-setup.md)
