.cursor/skills/dotnet-maui-aot/SKILL.md
Optimizing MAUI for iOS/Catalyst. Native AOT pipeline, size/startup gains, library gaps, trimming.
npx skillsauth add AGIBuild/Fulora dotnet-maui-aotInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Native AOT compilation for .NET MAUI on iOS and Mac Catalyst: compilation pipeline, publish profiles, up to 50% app size reduction and up to 50% startup improvement, library compatibility gaps, opt-out mechanisms, trimming interplay (RD.xml, source generators), and testing AOT builds on device.
Version assumptions: .NET 8.0+ baseline. Native AOT for MAUI is available on iOS and Mac Catalyst. Android uses a different compilation model (CoreCLR in .NET 11, Mono/AOT in .NET 8-10).
Scope boundary: This skill owns MAUI-specific Native AOT on iOS/Mac Catalyst -- the compilation pipeline, publish configuration, size/startup improvements, library compatibility for MAUI apps, and testing AOT builds. General Native AOT patterns are owned by [skill:dotnet-native-aot]; AOT architecture decisions by [skill:dotnet-aot-architecture].
Out of scope: MAUI development patterns (project structure, XAML, MVVM) -- see [skill:dotnet-maui-development]. MAUI testing -- see [skill:dotnet-maui-testing]. WASM AOT (Blazor/Uno) -- see [skill:dotnet-aot-wasm]. General AOT architecture -- see [skill:dotnet-native-aot].
Cross-references: [skill:dotnet-maui-development] for MAUI patterns, [skill:dotnet-maui-testing] for testing AOT builds, [skill:dotnet-native-aot] for general AOT patterns, [skill:dotnet-aot-wasm] for WASM AOT, [skill:dotnet-ui-chooser] for framework selection.
Native AOT on iOS and Mac Catalyst compiles .NET IL directly to native machine code at publish time, eliminating the need for a JIT compiler or IL interpreter at runtime. This produces a self-contained native binary that links against platform frameworks.
.app bundle with a native executable (no IL assemblies shipped)<!-- Enable Native AOT for iOS/Mac Catalyst -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net8.0-ios' Or
'$(TargetFramework)' == 'net8.0-maccatalyst'">
<PublishAot>true</PublishAot>
<!-- Optional: strip debug symbols for smaller binary -->
<StripSymbols>true</StripSymbols>
</PropertyGroup>
# Publish with AOT for iOS
dotnet publish -f net8.0-ios -c Release -r ios-arm64
# Publish with AOT for Mac Catalyst
dotnet publish -f net8.0-maccatalyst -c Release -r maccatalyst-arm64
# Publish for iOS simulator (for AOT testing without device)
dotnet publish -f net8.0-ios -c Release -r iossimulator-arm64
AOT builds require the same entitlements and provisioning profiles as regular iOS/Catalyst builds. No additional entitlements are needed for AOT specifically.
<!-- iOS entitlements (Entitlements.plist) -->
<!-- Standard entitlements; AOT does not require special entries -->
Native AOT can achieve up to 50% app size reduction compared to interpreter/JIT mode on iOS. The size improvement comes from:
| Mode | Approximate Size | Notes | |------|-----------------|-------| | Interpreter (default .NET 8 iOS) | ~60-80 MB | Includes IL assemblies + interpreter | | Native AOT | ~30-45 MB | Native binary only, no IL | | Native AOT + StripSymbols | ~25-40 MB | Debug symbols stripped |
Caveat: Actual size reduction depends on app complexity, third-party library usage, and how much code is reachable after trimming. Libraries that use heavy reflection may prevent aggressive trimming and reduce size gains.
Native AOT provides up to 50% faster cold startup on iOS and Mac Catalyst. The startup improvement comes from:
// Instrument startup timing
public partial class App : Application
{
private static readonly long StartTicks = Stopwatch.GetTimestamp();
public App()
{
InitializeComponent();
MainPage = new AppShell();
var elapsed = Stopwatch.GetElapsedTime(StartTicks);
System.Diagnostics.Debug.WriteLine(
$"App startup: {elapsed.TotalMilliseconds:F0}ms");
}
}
# Use Xcode Instruments for precise startup measurement
# Time Profiler template → measure "pre-main" + "post-main" time
# Compare AOT vs non-AOT builds on the same device
Many .NET libraries are not fully AOT-compatible. Common compatibility issues stem from:
Type.GetType(), Activator.CreateInstance()System.Reflection.Emit, System.Linq.Expressions.Compile()| Library / Feature | AOT Status | Workaround |
|------------------|------------|------------|
| System.Text.Json (source gen) | Compatible | Use [JsonSerializable] context |
| System.Text.Json (reflection) | Breaks | Switch to source generators |
| CommunityToolkit.Mvvm | Compatible | Source-gen based, AOT-safe |
| Entity Framework Core | Partial | Precompiled queries; no dynamic LINQ |
| Newtonsoft.Json | Breaks | Migrate to System.Text.Json with source gen |
| AutoMapper | Breaks | Use Mapperly (source gen) |
| MediatR | Partial | Register handlers explicitly, avoid assembly scanning |
| HttpClient | Compatible | Standard usage works |
| MAUI Essentials | Compatible | Platform APIs are AOT-safe |
| SQLite-net | Compatible | Uses P/Invoke, AOT-safe |
| Refit | Breaks | Use Refit 7+ (includes source generator; enable with [GenerateRefitClient]) |
| FluentValidation | Partial | Avoid runtime expression compilation |
<!-- Enable AOT analysis warnings during development -->
<PropertyGroup>
<EnableAotAnalyzer>true</EnableAotAnalyzer>
<!-- Also enable trim analyzer (AOT requires trimming) -->
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
</PropertyGroup>
AOT analysis produces warnings like IL3050 (RequiresDynamicCode) and IL2026 (RequiresUnreferencedCode). Address these before publishing with AOT.
<!-- Disable Native AOT (use interpreter/JIT mode) -->
<PropertyGroup>
<PublishAot>false</PublishAot>
</PropertyGroup>
When a specific library is not AOT-compatible, you can preserve it from trimming while still using AOT for the rest of the app:
<!-- Preserve a specific assembly from trimming -->
<ItemGroup>
<TrimmerRootAssembly Include="IncompatibleLibrary" />
</ItemGroup>
.NET 11 introduces new defaults that interact with AOT:
<!-- Revert XAML source gen (use legacy XAMLC) -->
<PropertyGroup>
<MauiXamlInflator>XamlC</MauiXamlInflator>
</PropertyGroup>
<!-- Revert to Mono runtime on Android (not related to iOS AOT,
but relevant for the overall MAUI AOT story) -->
<PropertyGroup>
<UseMonoRuntime>true</UseMonoRuntime>
</PropertyGroup>
Native AOT requires trimming. When PublishAot is true, trimming is automatically enabled. Understanding trimming configuration is essential for a successful AOT build.
Note: In Xamarin/Mono-era documentation, these were called "rd.xml" (Runtime Directives). In .NET 8+ Native AOT, use ILLink descriptor XML files instead.
When code uses reflection that the trimmer cannot statically analyze, use an ILLink descriptor XML file to preserve types. You can also use [DynamicDependency] attributes for fine-grained preservation in code.
ILLink descriptor XML (preferred for bulk preservation):
<!-- ILLink.Descriptors.xml -- preserve types needed at runtime -->
<linker>
<!-- Preserve all public members of a type -->
<assembly fullname="MyApp">
<type fullname="MyApp.Models.LegacyConfig" preserve="all" />
<type fullname="MyApp.Services.PluginLoader">
<method name="LoadPlugin" />
</type>
</assembly>
<!-- Preserve all types in an external assembly -->
<assembly fullname="IncompatibleLibrary" preserve="all" />
</linker>
<!-- Register the descriptor in .csproj -->
<ItemGroup>
<TrimmerRootDescriptor Include="ILLink.Descriptors.xml" />
</ItemGroup>
[DynamicDependency] attribute (preferred for targeted preservation):
using System.Diagnostics.CodeAnalysis;
// Preserve a specific method on a type
[DynamicDependency(nameof(LegacyConfig.Initialize), typeof(LegacyConfig))]
public void ConfigureApp() { /* ... */ }
// Preserve all public members of a type
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(LegacyConfig))]
public void LoadPlugins() { /* ... */ }
When source generators aren't available, use [DynamicDependency] attributes (shown above) for targeted preservation without ILLink XML files.
Prefer source generators over reflection to avoid trimming issues entirely:
| Reflection Pattern | Source Generator Alternative |
|-------------------|---------------------------|
| JsonSerializer.Deserialize<T>() | [JsonSerializable] context (System.Text.Json) |
| Activator.CreateInstance<T>() | Factory pattern with explicit registration |
| Type.GetProperties() | CommunityToolkit.Mvvm [ObservableProperty] |
| Assembly scanning for DI | Explicit services.Add*() registrations |
| AutoMapper reflection mapping | Mapperly [Mapper] source generator |
# Build with detailed trim warnings
dotnet publish -f net8.0-ios -c Release /p:PublishAot=true /p:TrimmerSingleWarn=false
# TrimmerSingleWarn=false shows per-occurrence warnings instead of
# one summary warning per assembly, making it easier to fix issues
Common trim warnings:
RequiresUnreferencedCode -- the member does something not guaranteed to work after trimmingRequiresDynamicCode -- the member generates code at runtime (incompatible with AOT)AOT builds can behave differently from Debug/JIT builds. Always test on a real device or simulator with an AOT-published build before release.
| Failure | Symptom | Fix |
|---------|---------|-----|
| Missing type metadata | MissingMetadataException at runtime | Add type to ILLink descriptor or use [DynamicDependency] |
| Trimmed method | MissingMethodException | Add [DynamicDependency] or ILLink descriptor entry |
| Dynamic code gen | PlatformNotSupportedException | Replace with source generator alternative |
| Reflection-based serialization | Empty/null deserialized objects | Use [JsonSerializable] source gen |
| Assembly scanning | Missing services at runtime | Register services explicitly in DI |
# 1. Build and publish with AOT for simulator (faster iteration)
dotnet publish -f net8.0-ios -c Release -r iossimulator-arm64
# 2. Install and test on simulator
# (Use Xcode or Visual Studio to deploy the .app to simulator)
# 3. Run smoke tests -- focus on:
# - App startup (no MissingMetadataException)
# - JSON deserialization (all properties populated)
# - Navigation (all pages render)
# - Platform services (biometric, camera, location)
# - Third-party SDK integration
# 4. Test on physical device before release
dotnet publish -f net8.0-ios -c Release -r ios-arm64
# Deploy via Xcode with provisioning profile
# CI pipeline: build AOT and run device tests via XHarness
dotnet publish -f net8.0-ios -c Release -r iossimulator-arm64 /p:PublishAot=true
xharness apple test \
--app bin/Release/net8.0-ios/iossimulator-arm64/publish/MyApp.app \
--target ios-simulator-64 \
--timeout 00:10:00 \
--output-directory test-results/aot
For MAUI testing patterns (Appium, XHarness), see [skill:dotnet-maui-testing].
PublishAot without also enabling trim analyzers. AOT requires trimming. Set <EnableTrimAnalyzer>true</EnableTrimAnalyzer> and <EnableAotAnalyzer>true</EnableAotAnalyzer> during development to catch issues early.IsAotCompatible in the package's .csproj or look for trim/AOT warnings when building. Many popular packages still use reflection internally.Newtonsoft.Json with AOT. It relies entirely on reflection. Migrate to System.Text.Json with [JsonSerializable] source gen contexts for AOT-safe serialization.PublishAot) targets iOS and Mac Catalyst only. Android uses a different compilation model (Mono AOT in .NET 8-10, CoreCLR in .NET 11+). They are configured separately.tools
Captures learnings, errors, and corrections to enable continuous improvement. Use when: (1) A command or operation fails unexpectedly, (2) User corrects Claude ('No, that's wrong...', 'Actually...'), (3) User requests a capability that doesn't exist, (4) An external API or tool fails, (5) Claude realizes its knowledge is outdated or incorrect, (6) A better approach is discovered for a recurring task. Also review learnings before major tasks.
testing
Security headers configuration and best practices for ASP.NET Core Razor Pages applications. Covers CSP, HSTS, X-Frame-Options, and comprehensive security middleware setup. Use when configuring security headers in ASP.NET Core applications, implementing Content Security Policy (CSP), or setting up HSTS and other security-related HTTP headers.
development
Reviews designs and business goals for security vulnerabilities, data protection (in transit/at rest), authorization, and compliance alignment. Use when the user asks for a security review, threat modeling, attack surface analysis, data leakage prevention, or compliance/security assessment.
development
Best practices for building production-grade ASP.NET Core Razor Pages applications. Focuses on structure, lifecycle, binding, validation, security, and maintainability in web apps using Razor Pages as the primary UI framework. Use when building Razor Pages applications, designing PageModels and handlers, implementing model binding and validation, or securing Razor Pages with authentication and authorization.