.NET MAUI Design Patterns

Choosing the right architectural pattern is crucial for building maintainable, testable, and scalable MAUI applications. This guide covers the most common patterns used in the .NET MAUI ecosystem.

Overview

MAUI supports several UI architectural patterns. The most widely adopted are:

Each pattern has its strengths and trade‑offs. Choose based on team familiarity, app complexity, and desired testability.

Model‑View‑ViewModel (MVVM)

MVVM separates UI (View) from business logic (ViewModel) and data (Model). It works naturally with XAML data‑binding.

public class MainViewModel : INotifyPropertyChanged
{
    private string _title = "Hello MAUI";
    public string Title
    {
        get => _title;
        set { _title = value; OnPropertyChanged(); }
    }

    public ICommand ClickCommand => new Command(() => Title = "Clicked!");
}

In XAML you bind directly to the ViewModel:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             x:Class="MyApp.MainPage"
             x:DataType="local:MainViewModel">
    <Label Text="{Binding Title}" />
    <Button Text="Press me" Command="{Binding ClickCommand}" />
</ContentPage>

See the full MVVM guide for dependency injection, navigation, and testing strategies.

Model‑View‑Update (MVU)

MVU treats the UI as a pure function of immutable state. Updates are performed via messages.

public record Model(int Counter);
public abstract record Msg;
public record Increment : Msg;
public record Decrement : Msg;

static Model Update(Msg msg, Model model) => msg switch
{
    Increment => model with { Counter = model.Counter + 1 },
    Decrement => model with { Counter = model.Counter - 1 },
    _ => model
};

static View BuildView(Model model) => new VStack
{
    new Label($"Count: {model.Counter}"),
    new Button("Increase", () => Dispatch(new Increment())),
    new Button("Decrease", () => Dispatch(new Decrement()))
};

Explore the MVU tutorial for a deeper dive, including state persistence and composition.

Model‑View‑Intent (MVI)

MVI extends MVU with side‑effects handling via intents. It’s useful for complex async flows.

public record State(bool IsLoading, string Data);
public abstract record Intent;
public record LoadData : Intent;

static async Task Reduce(Intent intent, State state) => intent switch
{
    LoadData => new State(true, state.Data),
    _ => state
};

static async Task PerformSideEffect(State state)
{
    var data = await Service.GetAsync();
    return new DataLoaded(data);
}

Read the MVI guide for implementation details.

Pattern Comparison

AspectMVVMMVUMVI
Learning CurveLowMediumHigh
BoilerplateModerateLowHigh
TestabilityHighVery HighVery High
Async HandlingVia CommandsMessage‑basedIntent‑based
Community SupportExtensiveGrowingNiche