# Maui Dependency Injection

> >

- **Type:** Skill
- **Install:** `agentstack add skill-dotnet-skills-maui-dependency-injection`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [dotnet](https://agentstack.voostack.com/s/dotnet)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [dotnet](https://github.com/dotnet)
- **Source:** https://github.com/dotnet/skills/tree/main/plugins/dotnet-maui/skills/maui-dependency-injection

## Install

```sh
agentstack add skill-dotnet-skills-maui-dependency-injection
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Dependency Injection in .NET MAUI

.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.

## When to Use

- Registering services, ViewModels, and Pages in `MauiProgram.cs`
- Choosing between `AddSingleton`, `AddTransient`, and `AddScoped`
- Wiring constructor injection for Pages and ViewModels
- Leveraging Shell navigation to auto-resolve DI-registered Pages
- Registering platform-specific service implementations with `#if` directives
- Designing interfaces for testable service layers

## When Not to Use

- XAML data-binding syntax or compiled bindings — use the **maui-data-binding** skill
- Shell route registration and query parameters — use the **maui-shell-navigation** skill
- Mocking frameworks or test runners — use standard .NET testing tools (xUnit, NUnit, MSTest) and mocking libraries (NSubstitute, Moq)

## Inputs

- A .NET MAUI project with a `MauiProgram.cs` file
- Knowledge of which services, ViewModels, and Pages need registration
- Target platforms (Android, iOS, Mac Catalyst, Windows) for conditional registrations

## Workflow

1. Identify all services, ViewModels, and Pages that need to participate in dependency injection.
2. Choose the correct lifetime for each type — `AddSingleton` for shared services, `AddTransient` for Pages and ViewModels.
3. Register all types in `MauiProgram.CreateMauiApp()` on `builder.Services`, grouping by category (services, HTTP, ViewModels, Pages).
4. Register Pages as Shell routes in `AppShell.xaml.cs` so Shell navigation auto-resolves the full dependency graph.
5. Wire each Page to its ViewModel via constructor injection, assigning the ViewModel as `BindingContext`.
6. Add platform-specific registrations with `#if` directives, ensuring every target platform is covered or has a fallback.
7. Verify resolution works by running the app and confirming no `null` dependencies or missing-registration exceptions at runtime.

---

## Lifetime Selection

| Lifetime | When to Use | Typical Types |
|---|---|---|
| `AddSingleton()` | Shared state, expensive to create, app-wide config | `HttpClient` factory, settings service, database connection |
| `AddTransient()` | Lightweight, stateless, or needs a fresh instance per use | Pages, ViewModels, per-call API wrappers |
| `AddScoped()` | Per-scope lifetime with manually created `IServiceScope` | Scoped unit-of-work (rare in MAUI) |

**Key rule:** Register Pages and ViewModels as **Transient**. Register shared services as **Singleton**.

> ⚠️ **Avoid `AddScoped` unless you manually manage `IServiceScope`.** MAUI has no built-in request scope like ASP.NET Core. A Scoped registration without an explicit scope silently behaves as a Singleton, leading to subtle bugs.

---

## Registration Pattern in MauiProgram.cs

```csharp
public static MauiApp CreateMauiApp()
{
    var builder = MauiApp.CreateBuilder();
    builder.UseMauiApp();

    // Services — Singleton for shared state
    builder.Services.AddSingleton();
    builder.Services.AddSingleton();

    // HTTP — use typed or named clients via IHttpClientFactory
    // Requires NuGet: Microsoft.Extensions.Http
    builder.Services.AddHttpClient();

    // ViewModels — Transient for fresh state per navigation
    builder.Services.AddTransient();
    builder.Services.AddTransient();

    // Pages — Transient so constructor injection fires each time
    builder.Services.AddTransient();
    builder.Services.AddTransient();

    return builder.Build();
}
```

---

## Constructor Injection

Inject dependencies through constructor parameters. The container resolves them automatically when the type is itself resolved from DI.

```csharp
public class MainViewModel
{
    private readonly IDataService _dataService;

    public MainViewModel(IDataService dataService)
    {
        _dataService = dataService;
    }

    public async Task LoadAsync() => Items = await _dataService.GetItemsAsync();
}
```

### ViewModel → Page Wiring

Register both Page and ViewModel. Inject the ViewModel into the Page and assign it as `BindingContext`:

```csharp
public partial class MainPage : ContentPage
{
    public MainPage(MainViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}
```

---

## Shell Navigation Auto-Resolution

When a Page is registered in DI **and** as a Shell route, Shell resolves it (and its full dependency graph) automatically on navigation:

```csharp
// MauiProgram.cs
builder.Services.AddTransient();
builder.Services.AddTransient();

// AppShell.xaml.cs
Routing.RegisterRoute(nameof(DetailPage), typeof(DetailPage));

// Navigate — DI resolves DetailPage + DetailViewModel
await Shell.Current.GoToAsync(nameof(DetailPage));
```

---

## Platform-Specific Registration

Use preprocessor directives to register platform implementations. Always cover every target platform or provide a no-op fallback to avoid runtime `null`.

```csharp
#if ANDROID
builder.Services.AddSingleton();
#elif IOS || MACCATALYST
builder.Services.AddSingleton();
#elif WINDOWS
builder.Services.AddSingleton();
#else
builder.Services.AddSingleton();
#endif
```

---

## Explicit Resolution (Last Resort)

Prefer constructor injection. Use explicit resolution only where injection is genuinely unavailable (custom handlers, platform callbacks):

```csharp
// From any Element with a Handler
var service = this.Handler.MauiContext.Services.GetService();
```

For dynamic resolution, inject `IServiceProvider`:

```csharp
public class NavigationService(IServiceProvider serviceProvider)
{
    public T ResolvePage() where T : Page
        => serviceProvider.GetRequiredService();
}
```

---

## Interface-First Pattern for Testability

Define interfaces for every service so implementations can be swapped in tests:

```csharp
public interface IDataService
{
    Task> GetItemsAsync();
}

// Production registration
builder.Services.AddSingleton();

// Test registration — swap without touching production code
var services = new ServiceCollection();
services.AddSingleton();
```

---

## Common Pitfalls

### 1. Singleton ViewModels Cause Stale Data

```csharp
// ❌ ViewModel keeps stale state across navigations
builder.Services.AddSingleton();

// ✅ Fresh instance each navigation
builder.Services.AddTransient();
```

### 2. Unregistered Page Silently Skips Injection

If a Page appears in Shell XAML via `` but is **not** registered in `builder.Services`, MAUI creates it with the parameterless constructor. Dependencies are silently `null` — no exception is thrown.

```csharp
// ❌ Missing — injection silently skipped
// builder.Services.AddTransient();

// ✅ Always register pages that need injection
builder.Services.AddTransient();
builder.Services.AddTransient();
```

### 3. XAML Resource Parsing vs. DI Timing

XAML resources in `App.xaml` are parsed during `InitializeComponent()` — before the container is fully available. Defer service-dependent work to `CreateWindow()`:

```csharp
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
        // Requires: builder.Services.AddTransient() in MauiProgram.cs
        var appShell = _services.GetRequiredService();
        return new Window(appShell);
    }
}
```

### 4. Service Locator Anti-Pattern

```csharp
// ❌ Hides dependencies, hard to test
var svc = this.Handler.MauiContext.Services.GetService();

// ✅ Constructor injection — explicit and testable
public class MyViewModel(IDataService dataService) { }
```

### 5. Missing Platform in Conditional Registration

Forgetting a platform in `#if` blocks means `GetService()` returns `null` at runtime on that platform. Always include an `#else` fallback or cover every target.

### 6. AddScoped Without Manual Scope

`AddScoped` in MAUI without creating `IServiceScope` manually gives Singleton behavior silently. Use `AddTransient` or `AddSingleton` instead unless you explicitly manage scopes.

---

## Checklist

- [ ] Every Page and ViewModel that needs injection is registered in `MauiProgram.cs`
- [ ] Pages and ViewModels use `AddTransient`; shared services use `AddSingleton`
- [ ] Constructor injection used everywhere possible; service locator only as last resort
- [ ] Interfaces defined for services that need test substitution
- [ ] Platform-specific `#if` registrations cover all target platforms or include a fallback
- [ ] Service-dependent work deferred to `CreateWindow()`, not run during XAML parse
- [ ] `AddScoped` only used alongside manually created `IServiceScope`

## References

- [Dependency injection in .NET MAUI](https://learn.microsoft.com/dotnet/maui/fundamentals/dependency-injection)
- [.NET dependency injection fundamentals](https://learn.microsoft.com/dotnet/core/extensions/dependency-injection)

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [dotnet](https://github.com/dotnet)
- **Source:** [dotnet/skills](https://github.com/dotnet/skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-dotnet-skills-maui-dependency-injection
- Seller: https://agentstack.voostack.com/s/dotnet
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
