关于 dotnet-maui-development
为使用 C# 和 XAML 构建跨平台应用的开发者提供 .NET MAUI 开发指导,涵盖项目结构、MVVM、平台服务、Hot Reload、迁移选择及 .NET 11 变化。
主要功能
- 01单项目多目标结构
- 02结合 MVVM 的 XAML 数据绑定
- 03Shell 导航与依赖注入
- 04平台专属服务模式
- 05Hot Reload 指导与注意事项
- 06迁移选择及 .NET 11 说明
适用场景
- 01规划多平台 MAUI 项目结构
- 02使用 XAML 和 ViewModel 实现共享界面
- 03为 MAUI 应用添加平台专属服务
- 04评估 MAUI 的适用性和迁移方案
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-maui-development安装后在 Claude Code 中输入/dotnet-maui-development确认已加载。
详细介绍
dotnet-maui-development
.NET MAUI 跨平台开发:采用平台文件夹的单项目结构、使用 MVVM(CommunityToolkit.Mvvm)的 XAML 数据绑定、Shell 导航、通过分部类和条件编译实现的平台服务、依赖注入、各平台上的 Hot Reload,以及 .NET 11 改进(XAML 源生成、Android 上的 CoreCLR、dotnet run 设备选择)。包含客观的当前状态评估和迁移选项。
**版本假设:**以 .NET 8.0+ 为基准(MAUI 随 .NET 8+ 一起提供)。明确标注 .NET 11 Preview 1 的内容。示例使用最新的稳定 API。
**范围界定:**此技能涵盖 MAUI 项目结构、XAML/MVVM 模式、平台服务、当前状态评估、迁移选项和 .NET 11 改进。iOS/Mac Catalyst 上的 MAUI Native AOT 由 [skill:dotnet-maui-aot] 负责。
**不在范围内:**MAUI 测试(Appium、XHarness、平台特定测试)——请参阅 [skill:dotnet-maui-testing]。通用 Native AOT 模式——请参阅 [skill:dotnet-native-aot]。UI 框架选择决策树——请参阅 [skill:dotnet-ui-chooser]。
交叉引用:[skill:dotnet-maui-aot](iOS/Mac Catalyst 上的 Native AOT)、[skill:dotnet-maui-testing](测试模式)、[skill:dotnet-version-detection](TFM 检测)、[skill:dotnet-native-aot](通用 AOT 模式)、[skill:dotnet-ui-chooser](框架选择)、[skill:dotnet-accessibility](无障碍模式,如 SemanticProperties、屏幕阅读器)。
项目结构
MAUI 使用单项目架构。一个 .csproj 通过多目标设定面向所有平台,平台特定代码则放在平台文件夹中。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">
$(TargetFrameworks);net8.0-windows10.0.19041.0
</TargetFrameworks>
<OutputType>Exe</OutputType>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<RootNamespace>MyApp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.*" />
<PackageReference Include="CommunityToolkit.Maui" Version="9.*" />
</ItemGroup>
</Project>
项目布局
MyApp/
MyApp/
App.xaml / App.xaml.cs # Application entry, resource dictionaries
AppShell.xaml / AppShell.xaml.cs # Shell navigation definition
MauiProgram.cs # Host builder, DI, service registration
MainPage.xaml / MainPage.xaml.cs # Initial page
ViewModels/ # MVVM ViewModels
Views/ # XAML pages
Models/ # Data models
Services/ # Service interfaces and implementations
Resources/
Fonts/ # Custom fonts (.ttf/.otf)
Images/ # SVG/PNG images (auto-resized per platform)
Styles/ # Shared styles, colors, resource dictionaries
Raw/ # Raw assets (JSON, etc.)
Splash/ # Splash screen image
Platforms/
Android/ # AndroidManifest.xml, MainActivity.cs
iOS/ # Info.plist, AppDelegate.cs
MacCatalyst/ # Info.plist, AppDelegate.cs
Windows/ # Package.appxmanifest, App.xaml
Properties/
launchSettings.json
MyApp.Tests/ # Unit tests
资源管理
MAUI 以声明式方式处理资源文件。图像会根据单一源文件自动调整大小,以适配各个平台:
<ItemGroup>
<MauiImage Include="Resources\Images\*" />
<MauiFont Include="Resources\Fonts\*" />
<MauiSplashScreen Include="Resources\Splash\splash.svg"
Color="#512BD4" BaseSize="128,128" />
<MauiIcon Include="Resources\AppIcon\appicon.svg"
ForegroundFile="Resources\AppIcon\appiconfg.svg"
Color="#512BD4" />
</ItemGroup>
XAML 模式
数据绑定基础
MAUI XAML 数据绑定将 UI 元素连接到 ViewModel 属性。使用 {Binding} 并正确设置 BindingContext。
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.Views.ProductListPage"
x:DataType="vm:ProductListViewModel">
<VerticalStackLayout Padding="16" Spacing="12">
<SearchBar Text="{Binding SearchTerm}"
SearchCommand="{Binding SearchCommand}" />
<CollectionView ItemsSource="{Binding Products}"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectProductCommand}"
SelectionChangedCommandParameter="{Binding SelectedItem,
Source={RelativeSource Self}}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Product">
<Frame Padding="12" Margin="0,4">
<HorizontalStackLayout Spacing="12">
<Image Source="{Binding ImageUrl}"
HeightRequest="60" WidthRequest="60" />
<VerticalStackLayout>
<Label Text="{Binding Name}"
FontAttributes="Bold" />
<Label Text="{Binding Price, StringFormat='{0:C}'}"
TextColor="Gray" />
</VerticalStackLayout>
</HorizontalStackLayout>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ContentPage>
**编译绑定:**在页面和数据模板上使用 x:DataType 启用编译绑定。编译绑定会在构建时进行类型检查,且运行速度比基于反射的绑定更快。
使用 CommunityToolkit.Mvvm 实现 MVVM
CommunityToolkit.Mvvm(Microsoft MVVM Toolkit)是 MAUI 推荐使用的 MVVM 框架。它使用源生成器来消除样板代码。
// ViewModels/ProductListViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class ProductListViewModel : ObservableObject
{
private readonly IProductService _productService;
private readonly INavigationService _navigationService;
public ProductListViewModel(
IProductService productService,
INavigationService navigationService)
{
_productService = productService;
_navigationService = navigationService;
}
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SearchCommand))]
private string _searchTerm = "";
[ObservableProperty]
private ObservableCollection<Product> _products = [];
[ObservableProperty]
private bool _isLoading;
[RelayCommand]
private async Task LoadProductsAsync(CancellationToken ct)
{
IsLoading = true;
try
{
var items = await _productService.GetProductsAsync(ct);
Products = new ObservableCollection<Product>(items);
}
finally
{
IsLoading = false;
}
}
[RelayCommand(CanExecute = nameof(CanSearch))]
private async Task SearchAsync(CancellationToken ct)
{
var results = await _productService.SearchAsync(SearchTerm, ct);
Products = new ObservableCollection<Product>(results);
}
private bool CanSearch() => !string.IsNullOrWhiteSpace(SearchTerm);
[RelayCommand]
private async Task SelectProductAsync(Product? product)
{
if (product is null) return;
await _navigationService.GoToAsync(
nameof(ProductDetailPage),
new Dictionary<string, object> { ["Product"] = product });
}
}
主要源生成器特性:
[ObservableProperty]—— 根据支持字段生成带有INotifyPropertyChanged的属性[RelayCommand]—— 根据方法生成ICommand(支持异步、取消和CanExecute)[NotifyPropertyChangedFor]—— 为依赖属性引发PropertyChanged[NotifyCanExecuteChangedFor]—— 属性更改时重新评估命令的CanExecute
Shell 导航
Shell 定义视觉层次结构和导航结构。它支持飞出菜单、选项卡和基于 URI 的导航。
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MyApp.Views"
x:Class="MyApp.AppShell">
<TabBar>
<ShellContent Title="Products"
Icon="products.png"
ContentTemplate="{DataTemplate views:ProductListPage}" />
<ShellContent Title="Cart"
Icon="cart.png"
ContentTemplate="{DataTemplate views:CartPage}" />
<ShellContent Title="Profile"
Icon="profile.png"
ContentTemplate="{DataTemplate views:ProfilePage}" />
</TabBar>
</Shell>
// Register routes for pages not in the Shell visual hierarchy
// (pushed onto the navigation stack, not direct Shell tabs)
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute(nameof(ProductDetailPage), typeof(ProductDetailPage));
Routing.RegisterRoute(nameof(CheckoutPage), typeof(CheckoutPage));
}
}
// Navigate with parameters
await Shell.Current.GoToAsync(nameof(ProductDetailPage),
new Dictionary<string, object>
{
["Product"] = selectedProduct
});
// Receive parameters via QueryProperty
[QueryProperty(nameof(Product), "Product")]
public partial class ProductDetailViewModel : ObservableObject
{
[ObservableProperty]
private Product _product = null!;
}
ContentPage 生命周期
public partial class ProductListPage : ContentPage
{
private readonly ProductListViewModel _viewModel;
public ProductListPage(ProductListViewModel viewModel)
{
InitializeComponent();
_viewModel = viewModel;
BindingContext = viewModel;
}
protected override async void OnAppearing()
{
base.OnAppearing();
// Load data when page appears (not in constructor)
await _viewModel.LoadProductsCommand.ExecuteAsync(null);
}
protected override void OnDisappearing()
{
base.OnDisappearing();
// Cancel pending operations, unsubscribe events
}
}
平台服务
用于平台特定代码的分部类
使用分部类,并在 Platforms 文件夹中提供平台特定实现。构建系统会为每个目标编译相应的实现。
// Services/IDeviceService.cs (shared interface)
public interface IDeviceService
{
string GetDeviceIdentifier();
Task<bool> HasBiometricAsync();
}
// Services/DeviceService.cs (shared partial class)
public partial class DeviceService : IDeviceService
{
public partial string GetDeviceIdentifier();
public partial Task<bool> HasBiometricAsync();
}
// Platforms/Android/Services/DeviceService.cs
public partial class DeviceService
{
public partial string GetDeviceIdentifier()
{
return Android.Provider.Settings.Secure.GetString(
Android.App.Application.Context.ContentResolver,
Android.Provider.Settings.Secure.AndroidId) ?? "unknown";
}
public partial Task<bool> HasBiometricAsync()
{
var manager = BiometricManager.From(Android.App.Application.Context);
var result = manager.CanAuthenticate(
BiometricManager.Authenticators.BiometricStrong);
return Task.FromResult(
result == BiometricManager.BiometricSuccess);
}
}
// Platforms/iOS/Services/DeviceService.cs
public partial class DeviceService
{
public partial string GetDeviceIdentifier()
{
return UIKit.UIDevice.CurrentDevice.IdentifierForVendor?.ToString()
?? "unknown";
}
public partial Task<bool> HasBiometricAsync()
{
var context = new LocalAuthentication.LAContext();
return Task.FromResult(context.CanEvaluatePolicy(
LocalAuthentication.LAPolicy.DeviceOwnerAuthenticationWithBiometrics,
out _));
}
}
条件编译
对于较小的平台差异,可使用 #if 指令,而非分部类:
public void ConfigurePlatformDefaults()
{
#if ANDROID
// Android-specific: request permissions
Platform.CurrentActivity?.RequestPermissions(
[Android.Manifest.Permission.Camera]);
#elif IOS || MACCATALYST
// iOS/Mac Catalyst: no runtime permission request needed for camera
// (handled via Info.plist NSCameraUsageDescription)
#elif WINDOWS
// Windows: WinUI-specific configuration
#endif
}
两种方式的适用场景:
- **分部类:**大型平台实现、多个方法、复杂逻辑
- **条件编译:**单行差异、轻微分支、常量
依赖注入
MAUI 使用 Microsoft.Extensions.DependencyInjection。在 MauiProgram.cs 中注册服务、ViewModel 和页面。
// MauiProgram.cs
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiCommunityToolkit()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
// Services
builder.Services.AddSingleton<IProductService, ProductService>();
builder.Services.AddSingleton<INavigationService, NavigationService>();
builder.Services.AddTransient<IDeviceService, DeviceService>();
// HTTP client
builder.Services.AddHttpClient("api", client =>
{
client.BaseAddress = new Uri("https://api.example.com");
});
// ViewModels (transient so each page gets a fresh instance)
builder.Services.AddTransient<ProductListViewModel>();
builder.Services.AddTransient<ProductDetailViewModel>();
// Pages (transient, resolved with DI-injected ViewModels)
builder.Services.AddTransient<ProductListPage>();
builder.Services.AddTransient<ProductDetailPage>();
// For DI patterns beyond MAUI-specific registration, see [skill:dotnet-csharp-dependency-injection]
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
当前状态评估(2026 年 2 月)
最后验证时间:2026-02-13
生产就绪情况
.NET MAUI 已可用于生产环境,但存在一些注意事项。该框架在企业中应用广泛,社区也在积极投入,但开发者应了解已知的工具和平台缺口。
增长指标:
- 用户同比增长 36%
- 社区拉取请求增加 557%
- 在业务线应用中得到广泛的企业采用
已知问题
Visual Studio 2026 工具问题:
- IDE 更新后,Android 工具链偶尔会在首次构建时失败;清理并重新构建通常可以解决大多数问题
- 在某些 Android 模拟器配置下,热重载偶尔无法连接
- XAML IntelliSense 可能会对有效的已编译绑定显示误报;即使出现红色波浪线,构建仍会成功
iOS 平台缺口:
- 兼容 iOS 26.x 需要使用最新的 MAUI 服务更新进行测试
- 某些 iOS 专用控件(例如使用自定义格式的
DatePicker)存在渲染不一致的问题 - Xcode 更新可能会导致构建工具链失效,直到 MAUI 发布服务更新
诚实评估
如果你需要使用单一的 C#/.NET 代码库,面向 Android、iOS、macOS (Catalyst) 和 Windows,并采用原生 UI 渲染,那么 MAUI 是合适的选择。它尤其适用于业务线应用、企业场景,以及拥有现有 .NET 专业经验的团队。
在以下情况下,MAUI 并非最佳选择:
- 需要面向 Web 浏览器的目标平台(可考虑 Blazor 或 Uno Platform)
- 需要 Linux 桌面支持(可考虑 Uno Platform 或 Avalonia)
- 需要跨平台实现像素级精准的自定义渲染(可考虑游戏引擎或基于 Skia 的框架)
- 团队没有 .NET 经验(可评估原生或 React Native 替代方案)
迁移选项
如果 MAUI 并非最佳选择,可考虑以下 .NET 替代方案:
WinUI 3(仅限 Windows)
如果你的应用仅面向 Windows,WinUI 3 无需跨平台抽象层,即可提供更丰富的 Windows 原生体验。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<UseWinUI>true</UseWinUI>
</PropertyGroup>
</Project>
适合选择 WinUI 而非 MAUI 的情况:
- 仅限 Windows 的应用,无移动端或 macOS 需求
- 需要深度集成 Windows(任务栏、通知、小组件)
- 需要 MAUI 未提供的 WinAppSDK 功能
- 从 UWP 迁移
Uno Platform(Web + Linux + 所有平台)
如果你除了移动端和 Windows,还需要支持 Web (WASM) 或 Linux 桌面,Uno Platform 可提供更广泛的目标平台覆盖,并支持兼容 WinUI 的 API 表面。
适合选择 Uno 而非 MAUI 的情况:
- 需要部署到 Web 浏览器(WASM)
- 需要 Linux 桌面支持(GTK/Framebuffer)
- 需要嵌入式设备目标平台
- 团队偏好使用 MVUX 响应式模式的 WinUI API 表面
- 需要 Figma 到 XAML 的设计工作流
有关 Uno Platform 开发模式,请参阅 [skill:dotnet-uno-platform];有关各目标平台的部署,请参阅 [skill:dotnet-uno-targets]。
决策摘要
| 需求 | MAUI | WinUI 3 | Uno Platform |
|---|---|---|---|
| Android + iOS | 是 | 否 | 是 |
| Windows 桌面 | 是 | 是(最佳) | 是 |
| macOS (Catalyst) | 是 | 否 | 是 |
| Web (WASM) | 否 | 否 | 是 |
| Linux 桌面 | 否 | 否 | 是 |
| 原生 UI 渲染 | 是 | 是 | Skia + 原生 |
| MVVM Toolkit | 是 | 是 | 是 |
| MVUX 响应式 | 否 | 否 | 是 |
完整的框架决策树请参阅 [skill:dotnet-ui-chooser]。
.NET 11 改进
最后验证时间:2026-02-13
XAML 源代码生成(.NET 11 Preview 1 中的默认模式)
.NET 11 Preview 1 将 XAML 源代码生成为默认的 XAML 编译模式,取代传统的 XAMLC(XamlCompilationAttribute)方式。源代码生成的 XAML 兼容 AOT,可提供更好的诊断信息,并加快启动速度。
<PropertyGroup>
<MauiXamlInflator>XamlC</MauiXamlInflator>
</PropertyGroup>
**变更内容:**XAML 页面会在生成时转换为 C# 源代码,而不是通过 XamlCompilationAttribute 编译为 IL。这会生成类型安全的初始化代码,并支持 Native AOT。
**何时回退:**如果自定义标记扩展遇到源代码生成问题、依赖运行时 XAML 加载的第三方控件出现问题,或使用旧版 LoadFromXaml,请回退到 XamlC。
Android 使用 CoreCLR(.NET 11 Preview 1 中的默认模式)
.NET 11 Preview 1 将 Android Release 构建的默认运行时从 Mono 换为 CoreCLR。CoreCLR 性能更佳、诊断能力更强,并与服务器和桌面运行时保持一致。
<PropertyGroup>
<UseMonoRuntime>true</UseMonoRuntime>
</PropertyGroup>
**变更内容:**Release 构建使用 CoreCLR(与 ASP.NET Core 和桌面应用使用相同的运行时),而不是 Mono。Debug 构建继续使用 Mono,以支持 Hot Reload。
**何时选择退出:**如果依赖 Mono 特有的行为、在较旧的 Android 设备(API < 26)上遇到 CoreCLR 兼容性问题,或使用专门针对 Mono 内部实现的库,请选择退出。
dotnet run 设备选择
.NET 11 Preview 1 为 MAUI 项目的 dotnet run 添加了交互式目标框架和设备选择功能。
# .NET 11: interactive device selection
dotnet run --project MyApp/MyApp.csproj
# Prompts: Select target framework:
# 1. net11.0-android
# 2. net11.0-ios
# 3. net11.0-maccatalyst
# Then: Select device:
# 1. Pixel 7 API 34 (emulator)
# 2. Samsung Galaxy S24 (physical)
# Skip interactive selection with explicit TFM
dotnet run --project MyApp/MyApp.csproj -f net11.0-android
这样就无需手动指定精确的 TFM 字符串 -f,并简化了开发者的内循环。
Hot Reload
MAUI 支持 XAML Hot Reload 和 C# Hot Reload,但具体功能因平台而异。
支持矩阵
| 更改类型 | Android | iOS | macOS | Windows |
|---|---|---|---|---|
| XAML 布局/样式 | 是 | 是 | 是 | 是 |
| C# 方法体 | 是 | 是 | 是 | 是 |
| 新增实例方法(非泛型类) | 部分支持(.NET 9+) | 部分支持(.NET 9+) | 部分支持(.NET 9+) | 部分支持(.NET 9+) |
| 新增静态方法/泛型类型成员 | 重新生成 | 重新生成 | 重新生成 | 重新生成 |
| 资源字典 | 是 | 是 | 是 | 是 |
| 新增 XAML 页面 | 重新生成 | 重新生成 | 重新生成 | 重新生成 |
| CSS 更改 | 是 | 是 | 是 | 是 |
启用 Hot Reload
# CLI: Hot Reload is enabled automatically in Debug configuration
dotnet run --project MyApp/MyApp.csproj -f net8.0-android
# Visual Studio: Hot Reload is on by default (fire icon in toolbar)
# VS Code: Use MAUI extension with Hot Reload enabled
注意事项:
- Hot Reload 需要 Debug 构建配置;Release 构建不支持
- XAML Hot Reload 对自定义渲染器或处理程序的更改可能要等到重新生成后才会生效
- 在 Android 上,Hot Reload 使用
MetadataUpdateHandler机制;对静态字段或构造函数的更改需要重启 - 在 iOS 模拟器上,Hot Reload 可以正常工作,但物理设备上的 Hot Reload 需要稳定的 USB/WiFi 连接
Agent 注意事项
- 不要创建单独的平台项目。 MAUI 采用单项目结构。平台专属代码应放在同一项目中的
Platforms/文件夹内,而不是放在单独的 Android/iOS 项目中(Xamarin.Forms 才是这种结构)。 - 不要混用 MVVM Toolkit 特性和手动实现的
INotifyPropertyChanged。 请始终使用[ObservableProperty]。混用源代码生成和手写的属性更改实现会导致难以察觉的绑定错误。 - 不要在构造函数中调用异步方法。 使用
OnAppearing()或已加载的命令触发数据加载。在构造函数中调用异步方法会导致未观察到的异常,以及与绑定上下文初始化相关的竞态条件。 - 不要使用
Device.BeginInvokeOnMainThread。 它已弃用。请改用Microsoft.Maui.ApplicationModel中的MainThread.BeginInvokeOnMainThread()或MainThread.InvokeOnMainThreadAsync()。 - 不要使用
RuntimeInformation硬编码平台检查。 请使用DeviceInfo.Platform比较(DevicePlatform.Android、DevicePlatform.iOS),这是 MAUI 用于平台检测的跨平台抽象。 - 不要在没有
x:DataType的情况下使用{Binding}。 始终在页面和数据模板上设置x:DataType,以启用编译绑定。基于反射的绑定速度较慢,且无法在构建时检测到错误。 - 页面通常应设为 Transient,而非 Singleton。 Singleton 页面会因保留的绑定导致数据过时和内存泄漏。如果需要保留状态(例如,选项卡式页面),请使用 Singleton ViewModel 和 Transient 页面。
- 不要忘记为非选项卡页面注册 Shell 路由。 推入导航堆栈的页面(通过
GoToAsync)必须在AppShell构造函数中使用Routing.RegisterRoute注册,否则导航会抛出RouteNotFoundException。
先决条件
- .NET 8.0+(.NET MAUI 随 .NET 8+ 一同提供)
- MAUI 工作负载:
dotnet workload install maui - 平台 SDK:Android SDK(API 21+)、Xcode(仅限 macOS,用于 iOS/Mac Catalyst)、Windows App SDK(用于 Windows)
- 安装了 MAUI 工作负载的 Visual Studio 2022+、安装了 .NET MAUI 扩展的 VS Code,或 JetBrains Rider 2024.2+