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 の Transient 登録
  3. 03CreateWindow までサービス解決を延期
  4. 04サービスロケーターの多用を回避
  5. 05プラットフォーム別登録の網羅性確認

活用シーン

  1. 01ページ遷移に応じた ViewModel のライフタイム設定
  2. 02ページへの依存性注入漏れの調査
  3. 03DI 準備前に解析される XAML リソースへの対処
  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 は Transient として登録してください。共有状態を保持するサービスは Singleton として登録してください(例: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 登録がすべての対象プラットフォームをカバーしている(またはフォールバックを提供している)
  • 遅延バインドされるサービスは、XAML の解析中ではなく CreateWindow() で解決されている
  • AddScoped は IServiceScope を手動で管理する場合にのみ使用している

コメント