MAUI App Architecture
Use this skill for app-level wiring: services, pages, ViewModels, bindings, and
navigation. Favor explicit, testable architecture over service locator patterns.
Workflow
- Inspect
MauiProgram.cs, AppShell.xaml, page constructors, and existing
ViewModels.
- Preserve the app's UI pattern: XAML/MVVM, C# Markup, MauiReactor, Blazor
Hybrid, or a mix.
- Register dependencies in
MauiProgram.cs.
- Use constructor injection for pages and ViewModels.
- Use compiled bindings with
x:DataType in pages and data templates.
- Register Shell routes once, near app startup. When asked how to register a
Shell route, show the literal
Routing.RegisterRoute(...) call, not only a
prose summary.
- Pass navigation data through Shell route query parameters. For trim-sensitive
or NativeAOT-sensitive flows, prefer
IQueryAttributable over
[QueryProperty].
- Keep platform APIs behind interfaces so ViewModels remain unit-testable.
Dependency Injection Guidance
| Dependency |
Typical lifetime |
| Stateless API clients and data services |
Singleton or typed HttpClient service |
| ViewModels with page state |
Transient |
| Pages |
Transient |
| User session/state service |
Singleton if intentionally app-wide |
| Disposable per-flow services |
Scoped only if the app has an explicit scope boundary |
Avoid calling BuildServiceProvider() inside MauiProgram.cs. Register the
type and let MAUI resolve it.
Shell Navigation Pattern
Routing.RegisterRoute(nameof(DetailsPage), typeof(DetailsPage));
await Shell.Current.GoToAsync($"{nameof(DetailsPage)}?id={Uri.EscapeDataString(id)}");
public sealed partial class DetailsViewModel : ObservableObject, IQueryAttributable
{
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
if (query.TryGetValue("id", out var value) && value is string id)
{
Load(Uri.UnescapeDataString(id));
}
}
}
Trim-safe Shell query parameters
[QueryProperty] is convenient, but it is not trim-safe.
- When full trimming or NativeAOT is in scope, use
IQueryAttributable on the
receiving page or ViewModel instead.
ApplyQueryAttributes keeps parsing, validation, conversion, and failure
handling explicit instead of relying on attribute-based property assignment.
- When values came from URI-based Shell navigation, string entries received via
IQueryAttributable are not automatically URL-decoded. Decode them
explicitly, for example with Uri.UnescapeDataString, before using them.
Compiled Binding Pattern
Compiled bindings with x:DataType give compile-time type checking, eliminate
reflection overhead, and can provide measurable startup and scrolling performance
improvements over reflection-based bindings.
<ContentPage
x:Class="MyApp.Views.ProductsPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:MyApp.ViewModels"
xmlns:models="clr-namespace:MyApp.Models"
x:DataType="viewModels:ProductsViewModel">
<CollectionView ItemsSource="{Binding Products}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Product">
<Label Text="{Binding Name}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
Migration from Older Patterns
| Older pattern |
Preferred MAUI pattern |
DependencyService.Get<T>() |
Register T in DI and inject it |
| Static service locators |
Constructor injection |
| Stringly typed BindingContext setup everywhere |
Page/ViewModel registration and compiled bindings |
| Unregistered Shell route strings |
Routing.RegisterRoute plus constants or nameof |
| Platform code in ViewModels |
Interface abstraction with platform implementations |
Validation Checklist
- Services, pages, and ViewModels are registered consistently.
- No new service locator or
BuildServiceProvider() usage was introduced.
- Pages and templates that bind to ViewModels/models have
x:DataType.
- Routes are registered before use and query values are encoded.
- Trim-sensitive or NativeAOT-sensitive Shell navigation uses
IQueryAttributable, and string URI query values are explicitly decoded.
- ViewModels remain testable without starting a MAUI app.
1---2name: maui-app-architecture3description: Design MAUI app architecture. USE FOR: DI/MauiProgram, MVVM page/ViewModel wiring, Shell routes/GoToAsync/query params, trim-safe `IQueryAttributable`, `x:DataType` compiled bindings, page lifetimes, avoiding service locators. DO NOT USE FOR: resources, API versioning, runtime debug tools.4---56# MAUI App Architecture78Use this skill for app-level wiring: services, pages, ViewModels, bindings, and9navigation. Favor explicit, testable architecture over service locator patterns.1011## Workflow12131. Inspect `MauiProgram.cs`, `AppShell.xaml`, page constructors, and existing14 ViewModels.152. Preserve the app's UI pattern: XAML/MVVM, C# Markup, MauiReactor, Blazor16 Hybrid, or a mix.173. Register dependencies in `MauiProgram.cs`.184. Use constructor injection for pages and ViewModels.195. Use compiled bindings with `x:DataType` in pages and data templates.206. Register Shell routes once, near app startup. When asked how to register a21 Shell route, show the literal `Routing.RegisterRoute(...)` call, not only a22 prose summary.237. Pass navigation data through Shell route query parameters. For trim-sensitive24 or NativeAOT-sensitive flows, prefer `IQueryAttributable` over25 `[QueryProperty]`.268. Keep platform APIs behind interfaces so ViewModels remain unit-testable.2728## Dependency Injection Guidance2930| Dependency | Typical lifetime |31| --- | --- |32| Stateless API clients and data services | Singleton or typed `HttpClient` service |33| ViewModels with page state | Transient |34| Pages | Transient |35| User session/state service | Singleton if intentionally app-wide |36| Disposable per-flow services | Scoped only if the app has an explicit scope boundary |3738Avoid calling `BuildServiceProvider()` inside `MauiProgram.cs`. Register the39type and let MAUI resolve it.4041## Shell Navigation Pattern4243```csharp44Routing.RegisterRoute(nameof(DetailsPage), typeof(DetailsPage));4546await Shell.Current.GoToAsync($"{nameof(DetailsPage)}?id={Uri.EscapeDataString(id)}");47```4849```csharp50public sealed partial class DetailsViewModel : ObservableObject, IQueryAttributable51{52 public void ApplyQueryAttributes(IDictionary<string, object> query)53 {54 if (query.TryGetValue("id", out var value) && value is string id)55 {56 Load(Uri.UnescapeDataString(id));57 }58 }59}60```6162## Trim-safe Shell query parameters6364- `[QueryProperty]` is convenient, but it is **not trim-safe**.65- When full trimming or NativeAOT is in scope, use `IQueryAttributable` on the66 receiving page or ViewModel instead.67- `ApplyQueryAttributes` keeps parsing, validation, conversion, and failure68 handling explicit instead of relying on attribute-based property assignment.69- When values came from URI-based Shell navigation, string entries received via70 `IQueryAttributable` are not automatically URL-decoded. Decode them71 explicitly, for example with `Uri.UnescapeDataString`, before using them.7273## Compiled Binding Pattern7475Compiled bindings with `x:DataType` give compile-time type checking, eliminate76reflection overhead, and can provide measurable startup and scrolling performance77improvements over reflection-based bindings.7879```xml80<ContentPage81 x:Class="MyApp.Views.ProductsPage"82 xmlns="http://schemas.microsoft.com/dotnet/2021/maui"83 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"84 xmlns:viewModels="clr-namespace:MyApp.ViewModels"85 xmlns:models="clr-namespace:MyApp.Models"86 x:DataType="viewModels:ProductsViewModel">87 <CollectionView ItemsSource="{Binding Products}">88 <CollectionView.ItemTemplate>89 <DataTemplate x:DataType="models:Product">90 <Label Text="{Binding Name}" />91 </DataTemplate>92 </CollectionView.ItemTemplate>93 </CollectionView>94</ContentPage>95```9697## Migration from Older Patterns9899| Older pattern | Preferred MAUI pattern |100| --- | --- |101| `DependencyService.Get<T>()` | Register `T` in DI and inject it |102| Static service locators | Constructor injection |103| Stringly typed BindingContext setup everywhere | Page/ViewModel registration and compiled bindings |104| Unregistered Shell route strings | `Routing.RegisterRoute` plus constants or `nameof` |105| Platform code in ViewModels | Interface abstraction with platform implementations |106107## Validation Checklist108109- Services, pages, and ViewModels are registered consistently.110- No new service locator or `BuildServiceProvider()` usage was introduced.111- Pages and templates that bind to ViewModels/models have `x:DataType`.112- Routes are registered before use and query values are encoded.113- Trim-sensitive or NativeAOT-sensitive Shell navigation uses114 `IQueryAttributable`, and string URI query values are explicitly decoded.115- ViewModels remain testable without starting a MAUI app.