[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DZFJTKXD4MHHMZS6WJHT6Y":3,"skill-same:01M6DZFJTKXD4MHHMZS6WJHT6Y":93},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":43,"linkOnly":44,"markdownContent":45,"markdownBody":46,"frontMatter":47,"refName":49,"commitSha":50,"skillDirectory":51,"forksCount":52,"repositoryHtmlUrl":53,"repositoryHomepageUrl":54,"ownerHtmlUrl":55,"ownerBlogUrl":56,"ownerDescription":57,"updatedAt":58,"install":59,"resources":72},"01M6DZFJTKXD4MHHMZS6WJHT6Y","maui-dependency-injection",null,"Guidance for configuring dependency injection in .NET MAUI apps — service registration in MauiProgram.cs, lifetime selection (Singleton \u002F Transient \u002F Scoped), constructor injection, Shell navigation auto-resolution, platform-specific registrations, and testability patterns. USE FOR: \"dependency injection\", \"DI setup\", \"AddSingleton\", \"AddTransient\", \"AddScoped\", \"service registration\", \"constructor injection\", \"IServiceProvider\", \"MauiProgram DI\", \"register services\", \"BindingContext injection\". DO NOT USE FOR: data binding (use maui-data-binding), Shell route configuration (use maui-shell-navigation), unit-test mocking frameworks (use standard xUnit and NSubstitute patterns).","managedcode\u002Fdotnet-skills","managedcode","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F78253589?v=4","01M6DYDJR3EG8KB5G4PC65XCTT","Managed Code","01M6DYDJX0ZT432N04NYJW89DN",515,"MIT","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet-maui\u002Fskills\u002Fmaui-dependency-injection\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42],"injection","dependency","maui dependency injection","maui","dependency injection","route","singleton","scoped","for","unit","register","apps","platform","resolution","test","frameworks","patterns","do","registration","selection","auto","use","xunit","data","2026-11-10 10:12:30",false,"---\nname: maui-dependency-injection\ndescription: >\n  Guidance for configuring dependency injection in .NET MAUI apps — service\n  registration in MauiProgram.cs, lifetime selection (Singleton \u002F Transient \u002F Scoped),\n  constructor injection, Shell navigation auto-resolution, platform-specific\n  registrations, and testability patterns.\n  USE FOR: \"dependency injection\", \"DI setup\", \"AddSingleton\", \"AddTransient\",\n  \"AddScoped\", \"service registration\", \"constructor injection\", \"IServiceProvider\",\n  \"MauiProgram DI\", \"register services\", \"BindingContext injection\".\n  DO NOT USE FOR: data binding (use maui-data-binding), Shell route configuration\n  (use maui-shell-navigation), unit-test mocking frameworks (use standard xUnit\n  and NSubstitute patterns).\nlicense: MIT\n---\n\n# Dependency Injection in .NET MAUI\n\n.NET MAUI uses the same `Microsoft.Extensions.DependencyInjection` container as ASP.NET Core. All service registration happens in `MauiProgram.CreateMauiApp()` on `builder.Services`. The container is built once at startup and is immutable thereafter.\n\n## When to Use\n\n- Registering services, ViewModels, and Pages in `MauiProgram.cs`\n- Choosing between `AddSingleton`, `AddTransient`, and `AddScoped`\n- Wiring constructor injection for Pages and ViewModels\n- Leveraging Shell navigation to auto-resolve DI-registered Pages\n- Registering platform-specific service implementations with `#if` directives\n- Designing interfaces for testable service layers\n\n## When Not to Use\n\n- XAML data-binding syntax or compiled bindings — use the **maui-data-binding** skill\n- Shell route registration and query parameters — use the **maui-shell-navigation** skill\n- Mocking frameworks or test runners — use standard .NET testing tools (xUnit, NUnit, MSTest) and mocking libraries (NSubstitute, Moq)\n\n## Inputs\n\n- A .NET MAUI project with a `MauiProgram.cs` file\n- Knowledge of which services, ViewModels, and Pages need registration\n- Target platforms (Android, iOS, Mac Catalyst, Windows) for conditional registrations\n\n## Rules That Change the Answer\n\n| Situation | Do this | Why |\n|---|---|---|\n| Registering a Page or ViewModel | Prefer `AddTransient` | A fresh instance per navigation avoids stale state, and a Singleton page cannot be re-added to the visual tree after it is removed. Singleton is defensible for a genuinely single-instance page (e.g. a root tab you want to keep warm) |\n| Registering shared\u002Fexpensive state | `AddSingleton` | One instance app-wide (settings, DB connection, `HttpClient` handler) |\n| Tempted to use `AddScoped` | Use `AddTransient` (or `AddSingleton` if sharing is intended) | MAUI has **no** built-in request scope like ASP.NET Core's HTTP pipeline. MAUI does create one `IServiceScope` per window, so a Scoped service lives as long as that window — and resolved from the root provider it behaves like a Singleton. Neither gives you per-navigation freshness |\n| Navigating to a DI-registered page | Register the page **and** its ViewModel, then `Routing.RegisterRoute` | `Shell.Current.GoToAsync` resolves the page through DI and injects its constructor dependencies |\n| Platform-specific implementation | `#if` per platform **with every platform covered** | A missing platform branch leaves the service unregistered and throws at resolution time |\n\n**Do not** introduce DI into a project that isn't using it, swap a working service\nlifetime, or add an interface purely for symmetry — only when the user asked or it\nfixes a real defect.\n\n**Answer narrowly, but completely.** When you recommend a lifetime change, show the\nregistration code, and give the realistic alternatives rather than a single verdict —\nfor a unit-of-work or `DbContext` question that means `AddTransient`, an explicit\n`IServiceScopeFactory.CreateScope()`, **and** the factory pattern\n(`AddDbContextFactory`), with a note on when each fits. A one-line prescription is\nusually a worse answer than a short menu with trade-offs.\n\n```csharp\n\u002F\u002F Explicit scope when you genuinely need unit-of-work semantics\nusing var scope = scopeFactory.CreateScope();\nvar db = scope.ServiceProvider.GetRequiredService\u003CMyDbContext>();\n```\n\n## Workflow\n\n1. Identify all services, ViewModels, and Pages that need to participate in dependency injection.\n2. Choose the correct lifetime for each type — `AddSingleton` for shared services, `AddTransient` for Pages and ViewModels.\n3. Register all types in `MauiProgram.CreateMauiApp()` on `builder.Services`, grouping by category (services, HTTP, ViewModels, Pages).\n4. Register Pages as Shell routes in `AppShell.xaml.cs` so Shell navigation auto-resolves the full dependency graph.\n5. Wire each Page to its ViewModel via constructor injection, assigning the ViewModel as `BindingContext`.\n6. Add platform-specific registrations with `#if` directives, ensuring every target platform is covered or has a fallback.\n7. Verify resolution works by running the app and confirming no `null` dependencies or missing-registration exceptions at runtime.\n\n---\n\n## Lifetime Selection\n\n| Lifetime | When to Use | Typical Types |\n|---|---|---|\n| `AddSingleton\u003CT>()` | Shared state, expensive to create, app-wide config | `HttpClient` factory, settings service, database connection |\n| `AddTransient\u003CT>()` | Lightweight, stateless, or needs a fresh instance per use | Pages, ViewModels, per-call API wrappers |\n| `AddScoped\u003CT>()` | Per-window lifetime, or a manually created `IServiceScope` | Scoped unit-of-work (rare in MAUI) |\n\n**Key rule:** Register Pages and ViewModels as **Transient** by default. Register shared services as **Singleton**.\n\n> ⚠️ **Avoid `AddScoped` unless you manually manage `IServiceScope`.** MAUI has no built-in request scope like ASP.NET Core. MAUI creates one `IServiceScope` per window, so a Scoped service lives as long as that window; resolved from the root provider it silently behaves as a Singleton. Neither gives per-navigation freshness.\n\n---\n\n## Registration Pattern in MauiProgram.cs\n\n```csharp\npublic static MauiApp CreateMauiApp()\n{\n    var builder = MauiApp.CreateBuilder();\n    builder.UseMauiApp\u003CApp>();\n\n    \u002F\u002F Services — Singleton for shared state\n    builder.Services.AddSingleton\u003CIDataService, DataService>();\n    builder.Services.AddSingleton\u003CISettingsService, SettingsService>();\n\n    \u002F\u002F HTTP — use typed or named clients via IHttpClientFactory\n    \u002F\u002F Requires NuGet: Microsoft.Extensions.Http\n    builder.Services.AddHttpClient\u003CIApiClient, ApiClient>();\n\n    \u002F\u002F ViewModels — Transient for fresh state per navigation\n    builder.Services.AddTransient\u003CMainViewModel>();\n    builder.Services.AddTransient\u003CDetailViewModel>();\n\n    \u002F\u002F Pages — Transient so constructor injection fires each time\n    builder.Services.AddTransient\u003CMainPage>();\n    builder.Services.AddTransient\u003CDetailPage>();\n\n    return builder.Build();\n}\n```\n\n---\n\n## Constructor Injection\n\nInject dependencies through constructor parameters. The container resolves them automatically when the type is itself resolved from DI.\n\n```csharp\npublic class MainViewModel\n{\n    private readonly IDataService _dataService;\n\n    public MainViewModel(IDataService dataService)\n    {\n        _dataService = dataService;\n    }\n\n    public async Task LoadAsync() => Items = await _dataService.GetItemsAsync();\n}\n```\n\n### ViewModel → Page Wiring\n\nRegister both Page and ViewModel. Inject the ViewModel into the Page and assign it as `BindingContext`:\n\n```csharp\npublic partial class MainPage : ContentPage\n{\n    public MainPage(MainViewModel viewModel)\n    {\n        InitializeComponent();\n        BindingContext = viewModel;\n    }\n}\n```\n\n---\n\n## Shell Navigation Auto-Resolution\n\nWhen a Page is registered in DI **and** as a Shell route, Shell resolves it (and its full dependency graph) automatically on navigation:\n\n```csharp\n\u002F\u002F MauiProgram.cs\nbuilder.Services.AddTransient\u003CDetailPage>();\nbuilder.Services.AddTransient\u003CDetailViewModel>();\n\n\u002F\u002F AppShell.xaml.cs\nRouting.RegisterRoute(nameof(DetailPage), typeof(DetailPage));\n\n\u002F\u002F Navigate — DI resolves DetailPage + DetailViewModel\nawait Shell.Current.GoToAsync(nameof(DetailPage));\n```\n\n### Passing parameters to a DI-resolved ViewModel\n\nDI supplies the ViewModel's *dependencies*; navigation parameters arrive separately.\nDon't try to inject them through the constructor — implement `IQueryAttributable`\non the ViewModel so it receives both:\n\n```csharp\npublic class DetailViewModel : ObservableObject, IQueryAttributable\n{\n    readonly IDataService _data;   \u002F\u002F ← injected by DI\n\n    public DetailViewModel(IDataService data) => _data = data;\n\n    public void ApplyQueryAttributes(IDictionary\u003Cstring, object> query)\n    {\n        \u002F\u002F ← supplied by navigation\n        if (query.TryGetValue(\"id\", out var id))\n            LoadAsync(id.ToString()!);\n    }\n}\n\n\u002F\u002F Navigate with a parameter — the page and its ViewModel still come from DI\nawait Shell.Current.GoToAsync($\"{nameof(DetailPage)}?id={product.Id}\");\n```\n\nShell applies query attributes to the page **and** its `BindingContext`, so the\nViewModel receives them without any wiring in the page.\n\n---\n\n## Platform-Specific Registration\n\nUse preprocessor directives to register platform implementations. Always cover every target platform or provide a no-op fallback to avoid runtime `null`.\n\n```csharp\n#if ANDROID\nbuilder.Services.AddSingleton\u003CINotificationService, AndroidNotificationService>();\n#elif IOS || MACCATALYST\nbuilder.Services.AddSingleton\u003CINotificationService, AppleNotificationService>();\n#elif WINDOWS\nbuilder.Services.AddSingleton\u003CINotificationService, WindowsNotificationService>();\n#else\nbuilder.Services.AddSingleton\u003CINotificationService, NoOpNotificationService>();\n#endif\n```\n\n---\n\n## Explicit Resolution (Last Resort)\n\nPrefer constructor injection. Use explicit resolution only where injection is genuinely unavailable (custom handlers, platform callbacks):\n\n```csharp\n\u002F\u002F From any Element with a Handler\nvar service = this.Handler.MauiContext.Services.GetService\u003CIDataService>();\n```\n\nFor dynamic resolution, inject `IServiceProvider`:\n\n```csharp\npublic class NavigationService(IServiceProvider serviceProvider)\n{\n    public T ResolvePage\u003CT>() where T : Page\n        => serviceProvider.GetRequiredService\u003CT>();\n}\n```\n\n---\n\n## Interface-First Pattern for Testability\n\nDefine interfaces for every service so implementations can be swapped in tests:\n\n```csharp\npublic interface IDataService\n{\n    Task\u003CList\u003CItem>> GetItemsAsync();\n}\n\n\u002F\u002F Production registration\nbuilder.Services.AddSingleton\u003CIDataService, DataService>();\n\n\u002F\u002F Test registration — swap without touching production code\nvar services = new ServiceCollection();\nservices.AddSingleton\u003CIDataService, FakeDataService>();\n```\n\n---\n\n## Common Pitfalls\n\n### 1. Singleton ViewModels Cause Stale Data\n\n```csharp\n\u002F\u002F ❌ ViewModel keeps stale state across navigations\nbuilder.Services.AddSingleton\u003CDetailViewModel>();\n\n\u002F\u002F ✅ Fresh instance each navigation\nbuilder.Services.AddTransient\u003CDetailViewModel>();\n```\n\n### 2. ContentTemplate Pages Are Not Created Through DI\n\nPages declared in Shell XAML via `\u003CShellContent ContentTemplate=\"{DataTemplate views:DetailPage}\">` are instantiated with `Activator.CreateInstance` (`ElementTemplate.cs`), **not** through the service provider. Constructor injection does not run on that path: if the page's only constructor takes dependencies, you get a `MissingMethodException` — not a silently `null` dependency.\n\nPages reached through `Routing.RegisterRoute` + `GoToAsync` are different: they go through `ActivatorUtilities.GetServiceOrCreateInstance` (`Routing.cs`), which injects registered dependencies even if the page type itself was never registered, and **throws** if a required dependency cannot be resolved.\n\n```csharp\n\u002F\u002F Registering the page and its dependencies keeps both paths working\nbuilder.Services.AddTransient\u003CDetailPage>();\nbuilder.Services.AddTransient\u003CDetailViewModel>();\n```\n\nIf you need DI for a tab\u002Fflyout page, give it a parameterless constructor that resolves what it needs, or navigate to it by route instead of embedding it in `ContentTemplate`.\n\n### 3. XAML Resource Parsing vs. DI Timing\n\nXAML resources in `App.xaml` are parsed during `InitializeComponent()` — before the container is fully available. Defer service-dependent work to `CreateWindow()`:\n\n```csharp\npublic partial class App : Application\n{\n    private readonly IServiceProvider _services;\n\n    public App(IServiceProvider services)\n    {\n        _services = services;\n        InitializeComponent();\n    }\n\n    protected override Window CreateWindow(IActivationState? activationState)\n    {\n        \u002F\u002F Safe — container is fully built\n        \u002F\u002F Requires: builder.Services.AddTransient\u003CAppShell>() in MauiProgram.cs\n        var appShell = _services.GetRequiredService\u003CAppShell>();\n        return new Window(appShell);\n    }\n}\n```\n\n### 4. Service Locator Anti-Pattern\n\n```csharp\n\u002F\u002F ❌ Hides dependencies, hard to test\nvar svc = this.Handler.MauiContext.Services.GetService\u003CIDataService>();\n\n\u002F\u002F ✅ Constructor injection — explicit and testable\npublic class MyViewModel(IDataService dataService) { }\n```\n\n### 5. Missing Platform in Conditional Registration\n\nForgetting a platform in `#if` blocks means `GetService\u003CT>()` returns `null` at runtime on that platform. Always include an `#else` fallback or cover every target.\n\n### 6. AddScoped Without Manual Scope\n\nSee the rule table above: `AddScoped` gives you either window lifetime or Singleton behaviour, never per-navigation freshness. Use `AddTransient` or `AddSingleton` unless you explicitly create and manage an `IServiceScope`.\n\n---\n\n## Checklist\n\n- [ ] Every Page and ViewModel that needs injection is registered in `MauiProgram.cs`\n- [ ] Pages and ViewModels use `AddTransient`; shared services use `AddSingleton`\n- [ ] Constructor injection used everywhere possible; service locator only as last resort\n- [ ] Interfaces defined for services that need test substitution\n- [ ] Platform-specific `#if` registrations cover all target platforms or include a fallback\n- [ ] Service-dependent work deferred to `CreateWindow()`, not run during XAML parse\n- [ ] `AddScoped` used only when window lifetime is intended, or alongside a manually created `IServiceScope`\n\n## References\n\n- [Dependency injection in .NET MAUI](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fmaui\u002Ffundamentals\u002Fdependency-injection)\n- [.NET dependency injection fundamentals](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fcore\u002Fextensions\u002Fdependency-injection)\n","# Dependency Injection in .NET MAUI\n\n.NET MAUI uses the same `Microsoft.Extensions.DependencyInjection` container as ASP.NET Core. All service registration happens in `MauiProgram.CreateMauiApp()` on `builder.Services`. The container is built once at startup and is immutable thereafter.\n\n## When to Use\n\n- Registering services, ViewModels, and Pages in `MauiProgram.cs`\n- Choosing between `AddSingleton`, `AddTransient`, and `AddScoped`\n- Wiring constructor injection for Pages and ViewModels\n- Leveraging Shell navigation to auto-resolve DI-registered Pages\n- Registering platform-specific service implementations with `#if` directives\n- Designing interfaces for testable service layers\n\n## When Not to Use\n\n- XAML data-binding syntax or compiled bindings — use the **maui-data-binding** skill\n- Shell route registration and query parameters — use the **maui-shell-navigation** skill\n- Mocking frameworks or test runners — use standard .NET testing tools (xUnit, NUnit, MSTest) and mocking libraries (NSubstitute, Moq)\n\n## Inputs\n\n- A .NET MAUI project with a `MauiProgram.cs` file\n- Knowledge of which services, ViewModels, and Pages need registration\n- Target platforms (Android, iOS, Mac Catalyst, Windows) for conditional registrations\n\n## Rules That Change the Answer\n\n| Situation | Do this | Why |\n|---|---|---|\n| Registering a Page or ViewModel | Prefer `AddTransient` | A fresh instance per navigation avoids stale state, and a Singleton page cannot be re-added to the visual tree after it is removed. Singleton is defensible for a genuinely single-instance page (e.g. a root tab you want to keep warm) |\n| Registering shared\u002Fexpensive state | `AddSingleton` | One instance app-wide (settings, DB connection, `HttpClient` handler) |\n| Tempted to use `AddScoped` | Use `AddTransient` (or `AddSingleton` if sharing is intended) | MAUI has **no** built-in request scope like ASP.NET Core's HTTP pipeline. MAUI does create one `IServiceScope` per window, so a Scoped service lives as long as that window — and resolved from the root provider it behaves like a Singleton. Neither gives you per-navigation freshness |\n| Navigating to a DI-registered page | Register the page **and** its ViewModel, then `Routing.RegisterRoute` | `Shell.Current.GoToAsync` resolves the page through DI and injects its constructor dependencies |\n| Platform-specific implementation | `#if` per platform **with every platform covered** | A missing platform branch leaves the service unregistered and throws at resolution time |\n\n**Do not** introduce DI into a project that isn't using it, swap a working service\nlifetime, or add an interface purely for symmetry — only when the user asked or it\nfixes a real defect.\n\n**Answer narrowly, but completely.** When you recommend a lifetime change, show the\nregistration code, and give the realistic alternatives rather than a single verdict —\nfor a unit-of-work or `DbContext` question that means `AddTransient`, an explicit\n`IServiceScopeFactory.CreateScope()`, **and** the factory pattern\n(`AddDbContextFactory`), with a note on when each fits. A one-line prescription is\nusually a worse answer than a short menu with trade-offs.\n\n```csharp\n\u002F\u002F Explicit scope when you genuinely need unit-of-work semantics\nusing var scope = scopeFactory.CreateScope();\nvar db = scope.ServiceProvider.GetRequiredService\u003CMyDbContext>();\n```\n\n## Workflow\n\n1. Identify all services, ViewModels, and Pages that need to participate in dependency injection.\n2. Choose the correct lifetime for each type — `AddSingleton` for shared services, `AddTransient` for Pages and ViewModels.\n3. Register all types in `MauiProgram.CreateMauiApp()` on `builder.Services`, grouping by category (services, HTTP, ViewModels, Pages).\n4. Register Pages as Shell routes in `AppShell.xaml.cs` so Shell navigation auto-resolves the full dependency graph.\n5. Wire each Page to its ViewModel via constructor injection, assigning the ViewModel as `BindingContext`.\n6. Add platform-specific registrations with `#if` directives, ensuring every target platform is covered or has a fallback.\n7. Verify resolution works by running the app and confirming no `null` dependencies or missing-registration exceptions at runtime.\n\n---\n\n## Lifetime Selection\n\n| Lifetime | When to Use | Typical Types |\n|---|---|---|\n| `AddSingleton\u003CT>()` | Shared state, expensive to create, app-wide config | `HttpClient` factory, settings service, database connection |\n| `AddTransient\u003CT>()` | Lightweight, stateless, or needs a fresh instance per use | Pages, ViewModels, per-call API wrappers |\n| `AddScoped\u003CT>()` | Per-window lifetime, or a manually created `IServiceScope` | Scoped unit-of-work (rare in MAUI) |\n\n**Key rule:** Register Pages and ViewModels as **Transient** by default. Register shared services as **Singleton**.\n\n> ⚠️ **Avoid `AddScoped` unless you manually manage `IServiceScope`.** MAUI has no built-in request scope like ASP.NET Core. MAUI creates one `IServiceScope` per window, so a Scoped service lives as long as that window; resolved from the root provider it silently behaves as a Singleton. Neither gives per-navigation freshness.\n\n---\n\n## Registration Pattern in MauiProgram.cs\n\n```csharp\npublic static MauiApp CreateMauiApp()\n{\n    var builder = MauiApp.CreateBuilder();\n    builder.UseMauiApp\u003CApp>();\n\n    \u002F\u002F Services — Singleton for shared state\n    builder.Services.AddSingleton\u003CIDataService, DataService>();\n    builder.Services.AddSingleton\u003CISettingsService, SettingsService>();\n\n    \u002F\u002F HTTP — use typed or named clients via IHttpClientFactory\n    \u002F\u002F Requires NuGet: Microsoft.Extensions.Http\n    builder.Services.AddHttpClient\u003CIApiClient, ApiClient>();\n\n    \u002F\u002F ViewModels — Transient for fresh state per navigation\n    builder.Services.AddTransient\u003CMainViewModel>();\n    builder.Services.AddTransient\u003CDetailViewModel>();\n\n    \u002F\u002F Pages — Transient so constructor injection fires each time\n    builder.Services.AddTransient\u003CMainPage>();\n    builder.Services.AddTransient\u003CDetailPage>();\n\n    return builder.Build();\n}\n```\n\n---\n\n## Constructor Injection\n\nInject dependencies through constructor parameters. The container resolves them automatically when the type is itself resolved from DI.\n\n```csharp\npublic class MainViewModel\n{\n    private readonly IDataService _dataService;\n\n    public MainViewModel(IDataService dataService)\n    {\n        _dataService = dataService;\n    }\n\n    public async Task LoadAsync() => Items = await _dataService.GetItemsAsync();\n}\n```\n\n### ViewModel → Page Wiring\n\nRegister both Page and ViewModel. Inject the ViewModel into the Page and assign it as `BindingContext`:\n\n```csharp\npublic partial class MainPage : ContentPage\n{\n    public MainPage(MainViewModel viewModel)\n    {\n        InitializeComponent();\n        BindingContext = viewModel;\n    }\n}\n```\n\n---\n\n## Shell Navigation Auto-Resolution\n\nWhen a Page is registered in DI **and** as a Shell route, Shell resolves it (and its full dependency graph) automatically on navigation:\n\n```csharp\n\u002F\u002F MauiProgram.cs\nbuilder.Services.AddTransient\u003CDetailPage>();\nbuilder.Services.AddTransient\u003CDetailViewModel>();\n\n\u002F\u002F AppShell.xaml.cs\nRouting.RegisterRoute(nameof(DetailPage), typeof(DetailPage));\n\n\u002F\u002F Navigate — DI resolves DetailPage + DetailViewModel\nawait Shell.Current.GoToAsync(nameof(DetailPage));\n```\n\n### Passing parameters to a DI-resolved ViewModel\n\nDI supplies the ViewModel's *dependencies*; navigation parameters arrive separately.\nDon't try to inject them through the constructor — implement `IQueryAttributable`\non the ViewModel so it receives both:\n\n```csharp\npublic class DetailViewModel : ObservableObject, IQueryAttributable\n{\n    readonly IDataService _data;   \u002F\u002F ← injected by DI\n\n    public DetailViewModel(IDataService data) => _data = data;\n\n    public void ApplyQueryAttributes(IDictionary\u003Cstring, object> query)\n    {\n        \u002F\u002F ← supplied by navigation\n        if (query.TryGetValue(\"id\", out var id))\n            LoadAsync(id.ToString()!);\n    }\n}\n\n\u002F\u002F Navigate with a parameter — the page and its ViewModel still come from DI\nawait Shell.Current.GoToAsync($\"{nameof(DetailPage)}?id={product.Id}\");\n```\n\nShell applies query attributes to the page **and** its `BindingContext`, so the\nViewModel receives them without any wiring in the page.\n\n---\n\n## Platform-Specific Registration\n\nUse preprocessor directives to register platform implementations. Always cover every target platform or provide a no-op fallback to avoid runtime `null`.\n\n```csharp\n#if ANDROID\nbuilder.Services.AddSingleton\u003CINotificationService, AndroidNotificationService>();\n#elif IOS || MACCATALYST\nbuilder.Services.AddSingleton\u003CINotificationService, AppleNotificationService>();\n#elif WINDOWS\nbuilder.Services.AddSingleton\u003CINotificationService, WindowsNotificationService>();\n#else\nbuilder.Services.AddSingleton\u003CINotificationService, NoOpNotificationService>();\n#endif\n```\n\n---\n\n## Explicit Resolution (Last Resort)\n\nPrefer constructor injection. Use explicit resolution only where injection is genuinely unavailable (custom handlers, platform callbacks):\n\n```csharp\n\u002F\u002F From any Element with a Handler\nvar service = this.Handler.MauiContext.Services.GetService\u003CIDataService>();\n```\n\nFor dynamic resolution, inject `IServiceProvider`:\n\n```csharp\npublic class NavigationService(IServiceProvider serviceProvider)\n{\n    public T ResolvePage\u003CT>() where T : Page\n        => serviceProvider.GetRequiredService\u003CT>();\n}\n```\n\n---\n\n## Interface-First Pattern for Testability\n\nDefine interfaces for every service so implementations can be swapped in tests:\n\n```csharp\npublic interface IDataService\n{\n    Task\u003CList\u003CItem>> GetItemsAsync();\n}\n\n\u002F\u002F Production registration\nbuilder.Services.AddSingleton\u003CIDataService, DataService>();\n\n\u002F\u002F Test registration — swap without touching production code\nvar services = new ServiceCollection();\nservices.AddSingleton\u003CIDataService, FakeDataService>();\n```\n\n---\n\n## Common Pitfalls\n\n### 1. Singleton ViewModels Cause Stale Data\n\n```csharp\n\u002F\u002F ❌ ViewModel keeps stale state across navigations\nbuilder.Services.AddSingleton\u003CDetailViewModel>();\n\n\u002F\u002F ✅ Fresh instance each navigation\nbuilder.Services.AddTransient\u003CDetailViewModel>();\n```\n\n### 2. ContentTemplate Pages Are Not Created Through DI\n\nPages declared in Shell XAML via `\u003CShellContent ContentTemplate=\"{DataTemplate views:DetailPage}\">` are instantiated with `Activator.CreateInstance` (`ElementTemplate.cs`), **not** through the service provider. Constructor injection does not run on that path: if the page's only constructor takes dependencies, you get a `MissingMethodException` — not a silently `null` dependency.\n\nPages reached through `Routing.RegisterRoute` + `GoToAsync` are different: they go through `ActivatorUtilities.GetServiceOrCreateInstance` (`Routing.cs`), which injects registered dependencies even if the page type itself was never registered, and **throws** if a required dependency cannot be resolved.\n\n```csharp\n\u002F\u002F Registering the page and its dependencies keeps both paths working\nbuilder.Services.AddTransient\u003CDetailPage>();\nbuilder.Services.AddTransient\u003CDetailViewModel>();\n```\n\nIf you need DI for a tab\u002Fflyout page, give it a parameterless constructor that resolves what it needs, or navigate to it by route instead of embedding it in `ContentTemplate`.\n\n### 3. XAML Resource Parsing vs. DI Timing\n\nXAML resources in `App.xaml` are parsed during `InitializeComponent()` — before the container is fully available. Defer service-dependent work to `CreateWindow()`:\n\n```csharp\npublic partial class App : Application\n{\n    private readonly IServiceProvider _services;\n\n    public App(IServiceProvider services)\n    {\n        _services = services;\n        InitializeComponent();\n    }\n\n    protected override Window CreateWindow(IActivationState? activationState)\n    {\n        \u002F\u002F Safe — container is fully built\n        \u002F\u002F Requires: builder.Services.AddTransient\u003CAppShell>() in MauiProgram.cs\n        var appShell = _services.GetRequiredService\u003CAppShell>();\n        return new Window(appShell);\n    }\n}\n```\n\n### 4. Service Locator Anti-Pattern\n\n```csharp\n\u002F\u002F ❌ Hides dependencies, hard to test\nvar svc = this.Handler.MauiContext.Services.GetService\u003CIDataService>();\n\n\u002F\u002F ✅ Constructor injection — explicit and testable\npublic class MyViewModel(IDataService dataService) { }\n```\n\n### 5. Missing Platform in Conditional Registration\n\nForgetting a platform in `#if` blocks means `GetService\u003CT>()` returns `null` at runtime on that platform. Always include an `#else` fallback or cover every target.\n\n### 6. AddScoped Without Manual Scope\n\nSee the rule table above: `AddScoped` gives you either window lifetime or Singleton behaviour, never per-navigation freshness. Use `AddTransient` or `AddSingleton` unless you explicitly create and manage an `IServiceScope`.\n\n---\n\n## Checklist\n\n- [ ] Every Page and ViewModel that needs injection is registered in `MauiProgram.cs`\n- [ ] Pages and ViewModels use `AddTransient`; shared services use `AddSingleton`\n- [ ] Constructor injection used everywhere possible; service locator only as last resort\n- [ ] Interfaces defined for services that need test substitution\n- [ ] Platform-specific `#if` registrations cover all target platforms or include a fallback\n- [ ] Service-dependent work deferred to `CreateWindow()`, not run during XAML parse\n- [ ] `AddScoped` used only when window lifetime is intended, or alongside a manually created `IServiceScope`\n\n## References\n\n- [Dependency injection in .NET MAUI](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fmaui\u002Ffundamentals\u002Fdependency-injection)\n- [.NET dependency injection fundamentals](https:\u002F\u002Flearn.microsoft.com\u002Fdotnet\u002Fcore\u002Fextensions\u002Fdependency-injection)\n",{"name":5,"license":15,"description":48},"Guidance for configuring dependency injection in .NET MAUI apps — service registration in MauiProgram.cs, lifetime selection (Singleton \u002F Transient \u002F Scoped), constructor injection, Shell navigation auto-resolution, platform-specific registrations, and testability patterns. USE FOR: \"dependency injection\", \"DI setup\", \"AddSingleton\", \"AddTransient\", \"AddScoped\", \"service registration\", \"constructor injection\", \"IServiceProvider\", \"MauiProgram DI\", \"register services\", \"BindingContext injection\". DO NOT USE FOR: data binding (use maui-data-binding), Shell route configuration (use maui-shell-navigation), unit-test mocking frameworks (use standard xUnit and NSubstitute patterns).\n","main","497cc9aba79a931ac6fb8d36a48cc22fe3d6d74b","external-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet-maui\u002Fskills\u002Fmaui-dependency-injection",41,"https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills","https:\u002F\u002Fskills.managed-code.com","https:\u002F\u002Fgithub.com\u002Fmanagedcode","https:\u002F\u002Fwww.managed-code.com","Open Source Community for .NET Developers: Reliable, Actively Maintained, and Community-Driven. Made in Ukraine 🇺🇦 with ❤️","2026-11-14 19:05:44",{"source":8,"skillName":5,"commands":60,"prompt":70,"zipAvailable":71},[61,64,67],{"tool":62,"command":63},"npx","npx skills add managedcode\u002Fdotnet-skills --skill maui-dependency-injection",{"tool":65,"command":66},"bunx","bunx skills add managedcode\u002Fdotnet-skills --skill maui-dependency-injection",{"tool":68,"command":69},"pnpm","pnpm dlx skills add managedcode\u002Fdotnet-skills --skill maui-dependency-injection","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet-maui\u002Fskills\u002Fmaui-dependency-injection\u002FSKILL.md",true,[73,79,85,89],{"path":74,"resourceType":75,"resourceTypeDesc":76,"sizeBytes":77,"sourceHtmlUrl":78},"core.py","SCRIPT","脚本",389,"https:\u002F\u002Fgithub.com\u002FiDorgham\u002FWorkspace-Factory\u002Fblob\u002Fmaster\u002Ffactory\u002Flibrary\u002Farchive\u002Flegacy_pillars\u002F10_engineering_devops\u002F01_software_engineering\u002Fbackend\u002Fskills\u002Fexternal\u002Fnestjs_architecture\u002Fnestjs-modular-architecture\u002Fcore.py",{"path":80,"resourceType":81,"resourceTypeDesc":82,"sizeBytes":83,"sourceHtmlUrl":84},"manifest.yaml","REFERENCE","参考文档",202,"https:\u002F\u002Fgithub.com\u002FiDorgham\u002FWorkspace-Factory\u002Fblob\u002Fmaster\u002Ffactory\u002Flibrary\u002Farchive\u002Flegacy_pillars\u002F10_engineering_devops\u002F01_software_engineering\u002Fbackend\u002Fskills\u002Fexternal\u002Fnestjs_architecture\u002Fnestjs-modular-architecture\u002Fmanifest.yaml",{"path":86,"resourceType":81,"resourceTypeDesc":82,"sizeBytes":87,"sourceHtmlUrl":88},"references\u002Fdependency-injection-api.md",4236,"https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet-maui\u002Fskills\u002Fmaui-dependency-injection\u002Freferences\u002Fdependency-injection-api.md",{"path":90,"resourceType":75,"resourceTypeDesc":76,"sizeBytes":91,"sourceHtmlUrl":92},"tests\u002Ftest_core.py",329,"https:\u002F\u002Fgithub.com\u002FiDorgham\u002FWorkspace-Factory\u002Fblob\u002Fmaster\u002Ffactory\u002Flibrary\u002Farchive\u002Flegacy_pillars\u002F10_engineering_devops\u002F01_software_engineering\u002Fbackend\u002Fskills\u002Fexternal\u002Fnestjs_architecture\u002Fnestjs-modular-architecture\u002Ftests\u002Ftest_core.py",[94,131,154,184,194,221],{"skillNo":95,"name":96,"nameZh":6,"description":97,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":98,"categories":99,"keywords":110,"publishedAt":43},"01M6DYQE1Y9FHAQHEDTVFQ3447","xtwitter-scraper","Use XTwitterScraper when a .NET application needs typed access to Xquik's X automation REST API for tweet search, user lookup, posting workflows, extraction jobs, monitors, webhooks, or giveaway draws. USE FOR: integrating the XTwitterScraper NuGet package; reviewing Xquik API client usage; modeling API-key configuration, pagination, binary exports, raw responses, retries, and webhook\u002Fevent flows in .NET. DO NOT USE FOR: unrelated stacks; direct browser automation; generic social-media strategy; or code that does not call Xquik. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fcatalog\u002FLibraries\u002FXTwitterScraper\u002Fskills\u002Fxtwitter-scraper\u002FSKILL.md",[100,105],{"categoryNo":101,"categoryName":102,"taxonomyType":103,"taxonomyTypeDesc":104},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":106,"categoryName":107,"taxonomyType":108,"taxonomyTypeDesc":109},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130],"xtwitter scraper","browser automation","workflows","run","code","browser","search","repository","api","Media","api client","access","edit","unrelated","webhooks","files","social","commands","modeling","webhook",{"skillNo":132,"name":133,"nameZh":6,"description":134,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":135,"categories":136,"keywords":143,"publishedAt":43},"01M6DYQ9S4PE1H6G8MCH9T21HR","sep","Use Sep for high-performance separated-value parsing and writing in .NET, including delimiter inference, explicit parser\u002Fwriter options, and low-allocation row\u002Fcolumn workflows. USE FOR: delimited data needs are performance-sensitive and allocation-aware; project needs explicit control over separator inference, escaping, trimming, and header behavior;. DO NOT USE FOR: unrelated stacks; generic tasks that do not need this specific guidance. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fcatalog\u002FLibraries\u002FSep\u002Fskills\u002Fsep\u002FSKILL.md",[137,140],{"categoryNo":138,"categoryName":139,"taxonomyType":103,"taxonomyTypeDesc":104},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":141,"categoryName":142,"taxonomyType":108,"taxonomyTypeDesc":109},"01JBSEEDCAT000000000000036","内容创作者",[133,144,145,113,146,114,118,42,147,148,123,149,124,126,36,150,151,152,128,153],"writing","performance","tasks","parsing","parser","context","writer","options","project","high-performance",{"skillNo":155,"name":156,"nameZh":6,"description":157,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":158,"categories":159,"keywords":160,"publishedAt":43},"01M6DZS2DNEPXCGGF6SSCW6G5V","webgpu-threejs-tsl","Comprehensive guide for developing WebGPU-enabled Three.js applications using TSL (Three.js Shading Language). Covers WebGPU renderer setup, TSL syntax and node materials, compute shaders, post-processing effects, and WGSL integration. Use this skill when working with Three.js WebGPU, TSL shaders, node materials, or GPU compute in Three.js.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fwebgpu-claude-skill\u002Fskills\u002Fwebgpu-threejs-tsl\u002FSKILL.md",[],[161,162,163,164,165,166,167,168,169,170,171,172,40,173,174,175,176,177,178,179,27,180,181,182,183],"webgpu","threejs","webgpu threejs tsl","setup","shaders","gpu","integration","applications","js","node","syntax","guide","language","three","compute","post","processing","with","renderer","materials","wgsl","effects","when",{"skillNo":185,"name":186,"nameZh":6,"description":187,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":188,"categories":189,"keywords":190,"publishedAt":43},"01M6DZS0BQXW2B56A289XF1X76","roslynk","Use Roslynk for semantic inspection and edits of *.cs, *.razor and *.cshtml solution files.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Froslynk\u002Fskills\u002Froslynk\u002FSKILL.md",[],[186,40,126,191,192,27,193],"semantic","razor","inspection",{"skillNo":195,"name":196,"nameZh":6,"description":197,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":198,"categories":199,"keywords":200,"publishedAt":43},"01M6DZRW6MEJCVW80K3S4XV60K","system-text-json-net11","Imperative guidance for the System.Text.Json APIs added in .NET 11: the built-in `JsonNamingPolicy.PascalCase` naming policy, and the strongly-typed generic `JsonSerializerOptions.GetTypeInfo` and `JsonSerializerOptions.TryGetTypeInfo` metadata accessors (generic overloads that return a typed `JsonTypeInfo`). USE ONLY when the user is targeting net11.0 or later and needs PascalCase JSON property or dictionary-key names without writing a custom naming policy, a strongly-typed generic `JsonTypeInfo` instead of the non-generic `JsonTypeInfo`, or a no-throw way to probe whether a type's serialization metadata is resolved. DO NOT USE when the target is earlier than net11.0, the requested behavior uses an established pre-net11 naming policy, or the user explicitly selected another JSON library.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet11\u002Fskills\u002Fsystem-text-json-net11\u002FSKILL.md",[],[201,202,203,204,144,205,206,207,208,209,210,40,211,212,213,214,36,215,216,217,218,27,219,220,183],"system","json","text","system text json net11","policy","metadata","library","serialization","behavior","user","only","probe","guidance","later","type","dictionary","naming","targeting","custom","generic",{"skillNo":222,"name":223,"nameZh":6,"description":224,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":225,"categories":226,"keywords":229,"publishedAt":43},"01M6DZRRJP3S662NFSG1RSPQQ7","process-api-net11","Provides guidance on the new System.Diagnostics.Process APIs introduced in .NET 11: Process.Run, Process.RunAndCaptureText, Process.StartAndForget, Process.ReadAllText\u002FBytes\u002FLines, KillOnParentExit, InheritedHandles, and StartDetached. USE FOR: starting, orchestrating, or capturing output from external processes in applications targeting net11.0 or later. DO NOT USE FOR: applications targeting .NET 10 or earlier, or basic pre-.NET 11 Process.Start usage without new APIs.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet11\u002Fskills\u002Fprocess-api-net11\u002FSKILL.md",[227,228],{"categoryNo":101,"categoryName":102,"taxonomyType":103,"taxonomyTypeDesc":104},{"categoryNo":106,"categoryName":107,"taxonomyType":108,"taxonomyTypeDesc":109},[119,230,231,232,114,233,168,201,40,213,214,36,234,235,236,237,218,27,238],"process","process api net11","diagnostics","start","basic","processes","output","usage","new"]