
Skill
maui-app-architecture
design .NET MAUI application architecture
Description
Design .NET MAUI architecture around DI, MVVM, compiled bindings, Shell navigation, route registration, query parameters, and testable services. USE FOR: MauiProgram service registration, page/ViewModel wiring, Shell GoToAsync, Routing.RegisterRoute, trim-safe IQueryAttributable vs [QueryProperty] for full trimming/NativeAOT, Uri.EscapeDataString/UnescapeDataString query handling, x:DataType on pages/DataTemplates, AddTransient page lifetimes, and avoiding BuildServiceProvider/service locator patterns. DO NOT USE FOR: project layout, API currency, or runtime debug tools.
SKILL.md
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:DataTypein 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
IQueryAttributableover[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
IQueryAttributableon the receiving page or ViewModel instead. ApplyQueryAttributeskeeps 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
IQueryAttributableare not automatically URL-decoded. Decode them explicitly, for example withUri.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.
More skills from the maui-labs repository
View all 32 skillsandroid-slim-bindings
create Android slim bindings for .NET
Jul 12.NETAndroidJavaKotlin +1devflow-automation
automate .NET MAUI app state
Jul 12.NETAutomationMAUIMobiledevflow-connect
diagnose DevFlow agent connectivity issues
Jul 12DebuggingMAUINetworkingdotnet-workload-info
discover MAUI workload metadata
Jul 12C#CLIEngineeringMAUIios-slim-bindings
create iOS slim bindings for MAUI
Jul 12.NETiOSMAUISwift +1maui-accessibility
implement accessibility in .NET MAUI apps
Jul 12.NETAccessibilityMAUIMobile +1
More from .NET (Microsoft)
View publishermultithreaded-task-migration
migrate MSBuild tasks to multithreaded mode
msbuild
Jul 22.NETEngineeringPerformanceanalyzing-dotnet-performance
analyze .NET code for performance anti-patterns
skills
Jul 12.NETCode AnalysisDebuggingPerformanceandroid-tombstone-symbolication
symbolicate .NET runtime frames in Android tombstones
skills
Jul 12.NETAndroidDebuggingMicrosoftapple-crash-symbolication
symbolicate .NET runtime frames in crash logs
skills
Jul 12.NETDebuggingiOSmacOS +1assertion-quality
evaluate assertion quality in test suites
skills
Jul 12Code AnalysisQATestingauthor-component
create and review Blazor components
skills
Jul 15.NETBlazorC#UI Components +1