| name | maui-dependency-injection |
| description | > Use when this capability is needed. |
Dependency Injection in .NET MAUI
Lifetime Decision Framework
| Question | Answer → Lifetime |
|---|
| Does it hold shared state or is expensive to create? | AddSingleton |
| Is it stateless, lightweight, or per-request? | AddTransient |
Do you manage IServiceScope yourself? | AddScoped |
⚠️ Avoid AddScoped in MAUI — there is no built-in scope per page.
Using it without manually creating IServiceScope gives you singleton
behaviour silently, which is confusing and error-prone.
Singleton traps
builder.Services.AddSingleton<DetailViewModel>();
builder.Services.AddTransient<DetailViewModel>();
Register Pages and ViewModels as Transient. Register services that hold
shared state as Singleton (e.g., IDataService, HttpClient factory).
Gotcha: XAML Resource Parsing vs. DI Timing
XAML resources (App.xaml styles, converters) are parsed during
InitializeComponent() — before the DI container is fully available. If a
resource or converter needs a service, resolve it in CreateWindow(), not
in the constructor.
public App(IDataService data)
{
InitializeComponent();
_data = data;
}
public partial class App : Application
{
private readonly IServiceProvider _services;
public App(IServiceProvider services)
{
_services = services;
InitializeComponent();
}
protected override Window CreateWindow(IActivationState? activationState)
{
var mainPage = _services.GetRequiredService<MainPage>();
return new Window(new AppShell());
}
}
Gotcha: Unregistered Page Silently Skips DI
If a Page is used in Shell XAML (<ShellContent ContentTemplate="...">) but
not registered in builder.Services, MAUI instantiates it with the
parameterless constructor. Dependencies are silently null — no exception.
builder.Services.AddTransient<DetailPage>();
builder.Services.AddTransient<DetailViewModel>();
Anti-Pattern: Service Locator Overuse
public void DoWork()
{
var service = this.Handler.MauiContext.Services.GetService<IDataService>();
service.Load();
}
public class MyViewModel(IDataService dataService)
{
public void DoWork() => dataService.Load();
}
Use explicit resolution (Handler.MauiContext.Services) only when constructor
injection is genuinely unavailable (e.g., inside a custom handler or
platform callback).
Platform-Specific Registration Pitfall
When using #if directives for platform services, ensure the interface
is always registered — otherwise consumers on untargeted platforms get a
runtime null.
#if ANDROID
builder.Services.AddSingleton<INotificationService, AndroidNotificationService>();
#elif IOS || MACCATALYST
builder.Services.AddSingleton<INotificationService, AppleNotificationService>();
#endif
#if ANDROID
builder.Services.AddSingleton<INotificationService, AndroidNotificationService>();
#elif IOS || MACCATALYST
builder.Services.AddSingleton<INotificationService, AppleNotificationService>();
#elif WINDOWS
builder.Services.AddSingleton<INotificationService, WindowsNotificationService>();
#endif
Checklist
Converted and distributed by TomeVault — claim your Tome and manage your conversions.