mcpField
M
maui-dependency-injection

maui-dependency-injection

github.com/davidortinau开发17772 次安装17 周前更新
代码仓库

关于 maui-dependency-injection

此技能为 .NET MAUI 开发者提供依赖注入生命周期和注册方面的指导。它聚焦页面与 ViewModel 的注册、XAML 初始化时序及平台特定服务的常见陷阱。

主要功能

  1. 01按状态和用途选择生命周期
  2. 02将页面与 ViewModel 注册为瞬态
  3. 03在 CreateWindow 中延迟解析服务
  4. 04避免过度使用服务定位器
  5. 05检查平台服务注册覆盖范围

适用场景

  1. 01配置页面导航时的 ViewModel 生命周期
  2. 02排查页面依赖未注入的问题
  3. 03处理 XAML 资源解析早于 DI 的情况
  4. 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