AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Maui Dependency Injection

skill-dotnet-skills-maui-dependency-injection · by dotnet

>

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-dotnet-skills-maui-dependency-injection

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-dotnet-skills-maui-dependency-injection)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Maui Dependency Injection? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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

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.

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:

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:

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

#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):

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

For dynamic resolution, inject IServiceProvider:

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:

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

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

// ❌ 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():

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

// ❌ 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

Source & license

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

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

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.