MVVM Pattern in .NET MAUI

Intermediate 30 min read Lesson 6 of 8

What is MVVM?

MVVM (Model-View-ViewModel) is an architectural pattern that separates the user interface (View) from the business logic (ViewModel) and data (Model). It enables better separation of concerns, testability, and maintainability.

MVVM Components
  • Model - Data and business logic
  • View - UI and user interaction
  • ViewModel - Bridge between Model and View

MVVM Architecture

// Project Structure
MyApp/
├── Models/
│   └── (Data models)
├── ViewModels/
│   └── (View models)
├── Views/
│   └── (XAML pages)
└── Services/
    └── (Data services)

Implementing MVVM

1. The Model

// Models/Product.cs
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    public int Quantity { get; set; }
    public string Category { get; set; }
    public DateTime CreatedDate { get; set; }
}

2. The ViewModel

// ViewModels/ProductViewModel.cs
using System.Collections.ObjectModel;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

public partial class ProductViewModel : ObservableObject
{
    [ObservableProperty]
    private ObservableCollection products;
    
    [ObservableProperty]
    private Product selectedProduct;
    
    [ObservableProperty]
    private string searchText;
    
    [ObservableProperty]
    private bool isLoading;
    
    private readonly IProductService productService;
    
    public ProductViewModel(IProductService productService)
    {
        this.productService = productService;
        Products = new ObservableCollection();
        LoadProductsCommand = new AsyncRelayCommand(LoadProductsAsync);
        SaveProductCommand = new AsyncRelayCommand(SaveProductAsync);
        DeleteProductCommand = new AsyncRelayCommand(DeleteProductAsync);
    }
    
    public IAsyncRelayCommand LoadProductsCommand { get; }
    public IAsyncRelayCommand SaveProductCommand { get; }
    public IAsyncRelayCommand DeleteProductCommand { get; }
    
    private async Task LoadProductsAsync()
    {
        try
        {
            IsLoading = true;
            var items = await productService.GetProductsAsync();
            Products.Clear();
            foreach (var item in items)
                Products.Add(item);
        }
        finally
        {
            IsLoading = false;
        }
    }
    
    private async Task SaveProductAsync()
    {
        if (SelectedProduct == null) return;
        await productService.SaveProductAsync(SelectedProduct);
        await LoadProductsAsync();
    }
    
    private async Task DeleteProductAsync()
    {
        if (SelectedProduct == null) return;
        await productService.DeleteProductAsync(SelectedProduct.Id);
        await LoadProductsAsync();
    }
}

3. The View

// Views/ProductsPage.xaml


    
    
        
    
    
    
        
        
        
        
        
        
            
                
                    
                        
                            
                            
                        
                        
                        
                            
                        
                        
                
            
        
        
        
        
            

Data Binding

// One-way binding

Command Binding

// ViewModel
public ICommand SaveCommand { get; }

public ProductViewModel()
{
    SaveCommand = new RelayCommand(Save, CanSave);
}

private bool CanSave()
{
    return !string.IsNullOrEmpty(SelectedProduct?.Name);
}

private void Save()
{
    // Save logic
}

// View

Dependency Injection with MVVM

// Services registration in MauiProgram.cs
builder.Services.AddSingleton();
builder.Services.AddSingleton();

// ViewModel injection
public partial class ProductsPage : ContentPage
{
    public ProductsPage(ProductViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}

// Using Service Provider
var viewModel = ServiceHelper.GetService();
Key Takeaway
  • MVVM separates UI from business logic
  • Data binding connects View and ViewModel
  • Commands handle user actions
  • Dependency injection improves testability
Exercise

Implement MVVM for a simple app:

  1. Create Model for your data
  2. Implement ViewModel with properties and commands
  3. Create View with XAML
  4. Implement data binding
  5. Add CRUD operations
  6. Implement validation
Test Your Knowledge - Take Quiz