关于 maui-dependency-injection
此技能为 .NET MAUI 开发者提供依赖注入生命周期和注册方面的指导。它聚焦页面与 ViewModel 的注册、XAML 初始化时序及平台特定服务的常见陷阱。
主要功能
- 01按状态和用途选择生命周期
- 02将页面与 ViewModel 注册为瞬态
- 03在 CreateWindow 中延迟解析服务
- 04避免过度使用服务定位器
- 05检查平台服务注册覆盖范围
适用场景
- 01配置页面导航时的 ViewModel 生命周期
- 02排查页面依赖未注入的问题
- 03处理 XAML 资源解析早于 DI 的情况
- 04避免平台条件编译造成服务缺失
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ davidortinau/ maui-skills --skill maui-dependency-injection安装后在 Claude Code 中输入/maui-dependency-injection确认已加载。
附带文件references/dependency-injection-api.md
详细介绍
.NET MAUI 中的依赖注入
生命周期决策框架
| 问题 | 答案 → 生命周期 |
|---|---|
| 它是否持有共享状态,或创建成本是否很高? | AddSingleton |
| 它是否无状态、轻量,或按请求创建? | AddTransient |
你是否自行管理 IServiceScope? | AddScoped |
⚠️ 在 MAUI 中避免使用
AddScoped——每个页面都没有内置作用域。 不手动创建IServiceScope就使用它,会悄无声息地表现为单例, 令人困惑且容易出错。
单例陷阱
// ❌ ViewModel registered as Singleton — stale data across navigations
builder.Services.AddSingleton<DetailViewModel>();
// ✅ ViewModels are Transient — fresh instance each navigation
builder.Services.AddTransient<DetailViewModel>();
将 页面和 ViewModel 注册为瞬态。将 持有共享状态的服务注册为单例(例如
IDataService、HttpClient工厂)。
易错点:XAML 资源解析时机与 DI 时机
XAML 资源(App.xaml 样式、转换器)会在 InitializeComponent() 期间解析——此时 DI 容器尚未完全可用。如果资源或转换器需要服务,请在 CreateWindow() 中解析,而不是在构造函数中解析。
// ❌ Resolving services during XAML parse — container may not be ready
public App(IDataService data)
{
InitializeComponent(); // XAML parses here
_data = data; // may fail for types not yet resolved
}
// ✅ Defer service resolution to CreateWindow
public partial class App : Application
{
private readonly IServiceProvider _services;
public App(IServiceProvider services)
{
_services = services;
InitializeComponent();
}
protected override Window CreateWindow(IActivationState? activationState)
{
// Safe — container is fully built
var mainPage = _services.GetRequiredService<MainPage>();
return new Window(new AppShell());
}
}
易错点:未注册的页面会静默跳过 DI
如果某个页面在 Shell XAML(<ShellContent ContentTemplate="...">)中使用,但未在 builder.Services 中注册,MAUI 会使用无参构造函数实例化该页面。依赖项会静默地变成 null——不会抛出异常。
// ❌ Page not registered — constructor injection silently skipped
// builder.Services.AddTransient<DetailPage>(); // missing!
// ✅ Always register pages that need injection
builder.Services.AddTransient<DetailPage>();
builder.Services.AddTransient<DetailViewModel>();
反模式:过度使用服务定位器
// ❌ Service locator scattered through code — hard to test, hides dependencies
public void DoWork()
{
var service = this.Handler.MauiContext.Services.GetService<IDataService>();
service.Load();
}
// ✅ Constructor injection — explicit, testable
public class MyViewModel(IDataService dataService)
{
public void DoWork() => dataService.Load();
}
仅在确实无法使用构造函数注入时,才使用显式解析(
Handler.MauiContext.Services)(例如,在自定义处理程序或平台回调中)。
特定平台注册易错点
使用 #if 指令注册平台服务时,请确保始终注册接口,否则未指定目标平台上的使用方会在运行时得到 null。
// ❌ No registration on Windows — GetService returns null
#if ANDROID
builder.Services.AddSingleton<INotificationService, AndroidNotificationService>();
#elif IOS || MACCATALYST
builder.Services.AddSingleton<INotificationService, AppleNotificationService>();
#endif
// ✅ Cover all platforms or provide a no-op fallback
#if ANDROID
builder.Services.AddSingleton<INotificationService, AndroidNotificationService>();
#elif IOS || MACCATALYST
builder.Services.AddSingleton<INotificationService, AppleNotificationService>();
#elif WINDOWS
builder.Services.AddSingleton<INotificationService, WindowsNotificationService>();
#endif
检查清单
- 需要注入的每个页面和 ViewModel 都已在
MauiProgram.cs中注册 - 页面/ViewModel 使用
AddTransient;共享服务使用AddSingleton - 尽可能在各处使用构造函数注入;仅在最后不得已时使用服务定位器
- 为需要在测试中模拟的服务定义接口
- 特定平台的
#if注册涵盖所有目标平台(或提供回退方案) - 在
CreateWindow()中解析延迟绑定的服务,而不是在解析 XAML 时解析 - 仅在手动管理
IServiceScope时使用AddScoped
评论
支持 Markdown