.NET MAUI
Dependency Injection & Services
7 question(s)
How is dependency injection set up in MAUI?
Beginner
MAUI uses Microsoft.Extensions.DependencyInjection through the Generic Host. Register services on builder.Services in MauiProgram.CreateMauiApp, then constructor-inject them into pages and view models. The container resolves the whole graph when a registered page is created.
builder.Services.AddSingleton<IApiClient, ApiClient>();
builder.Services.AddTransient<MainViewModel>();
builder.Services.AddTransient<MainPage>();
Real-world example
Every page receives its view model and services through the constructor, so nothing is newed up manually in the UI layer.
Common follow-ups: Where do you register services? | How does a page get its dependencies?
Dependency Injection
MauiProgram
MVVM
What are the DI service lifetimes and when do you use each?
Beginner
Singleton creates one instance for the app's lifetime—good for shared state, HTTP clients, and caches. Transient creates a new instance per resolution—good for view models and lightweight services. Scoped is per-scope (used more in Blazor Hybrid). Mismatched lifetimes (a singleton depending on a transient) can cause captive dependencies.
builder.Services.AddSingleton<ISettingsService, SettingsService>();
builder.Services.AddTransient<DetailsViewModel>();
Real-world example
A settings service is a singleton so all pages read the same values, while each details view model is transient to avoid stale state between visits.
Common follow-ups: What is a captive dependency? | Which lifetime fits a view model?
Dependency Injection
MVVM
App Lifecycle
How do you inject a view model into a page?
Intermediate
Register both the page and the view model in DI, then take the view model as a constructor parameter of the page and assign it to BindingContext. Because the page is resolved from the container (e.g., via Shell route registration or GoToAsync), the view model is injected automatically.
public MainPage(MainViewModel vm)
{
InitializeComponent();
BindingContext = vm;
}
Real-world example
Navigating to MainPage yields a fully wired view model with its repository and logger injected, without service-locator calls.
Common follow-ups: Do you need to register the page too? | How does Shell resolve pages?
Dependency Injection
MVVM
Shell Navigation
What is the service locator anti-pattern and how do you avoid it in MAUI?
Intermediate
Service location (e.g., calling a static Resolve or Handler.MauiContext.Services.GetService) hides dependencies and hurts testability. Prefer constructor injection so a class's needs are explicit. When you truly can't inject (e.g., in a platform static context), isolate the lookup behind an interface and keep it out of business logic.
// Prefer this
public OrdersViewModel(IOrderService svc) { _svc = svc; }
// Avoid scattering this
var svc = Application.Current.Handler.MauiContext.Services
.GetService<IOrderService>();
Real-world example
A code review rejects a view model that pulls services from a static locator in favor of explicit constructor parameters that tests can supply mocks for.
Common follow-ups: When is service location unavoidable? | Why does DI help testing?
Dependency Injection
Testing & Debugging
MVVM
How do you register and use HttpClient with IHttpClientFactory in MAUI?
Intermediate
Call builder.Services.AddHttpClient to register typed or named clients backed by IHttpClientFactory, which manages handler pooling and lifetime. Inject the typed client into services. This avoids socket exhaustion from newing HttpClient and centralizes base addresses and default headers.
builder.Services.AddHttpClient<IApiClient, ApiClient>(c =>
{
c.BaseAddress = new Uri("https://api.example.com");
c.Timeout = TimeSpan.FromSeconds(30);
});
Real-world example
An app's API layer gets pooled, correctly-configured HttpClients through the factory rather than one leaked static instance.
Common follow-ups: Why not new up HttpClient? | What is a typed client?
Networking & APIs
Dependency Injection
Performance & Optimization
How do you resolve services from a static or platform context where constructor injection isn't possible?
Advanced
Expose the app's IServiceProvider. A common approach is a static helper that reads IPlatformApplication.Current.Services, used sparingly at boundaries (e.g., inside a platform callback). Keep such access at the edge and pass resolved services into normal injected code.
public static class ServiceHelper
{
public static T Get<T>() where T : notnull =>
IPlatformApplication.Current!.Services.GetRequiredService<T>();
}
Real-world example
A push-notification native callback, which can't use constructor injection, uses ServiceHelper.Get to obtain the message handler and then delegates to injected logic.
Common follow-ups: Why keep this at the boundary? | What is IPlatformApplication?
Dependency Injection
Platform Integration
App Lifecycle
How do you implement a messaging/event aggregator to decouple view models?
Advanced
Use CommunityToolkit.Mvvm's WeakReferenceMessenger to publish and subscribe to messages without direct references, avoiding memory leaks from strong event subscriptions. One view model sends a message; others register handlers. Unregister when appropriate, though weak references reduce leak risk.
WeakReferenceMessenger.Default.Send(new ItemAddedMessage(item));
WeakReferenceMessenger.Default.Register<ItemAddedMessage>(this,
(r, m) => Items.Add(m.Value));
Real-world example
Adding an item on an edit page notifies the list page to refresh via a message, with neither page holding a reference to the other.
Common follow-ups: Why weak references? | When should you unregister?
MVVM Toolkit
Dependency Injection
Performance & Optimization