maui-dependency-injection
npx skills add https://github.com/davidortinau/maui-skills --skill maui-dependency-injection
Agent 安装分布
Skill 文档
Dependency Injection in .NET MAUI
Service Registration in MauiProgram.cs
Register services on builder.Services inside CreateMauiApp():
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>();
// Services
builder.Services.AddSingleton<IDataService, DataService>();
builder.Services.AddTransient<IApiClient, ApiClient>();
// ViewModels
builder.Services.AddTransient<MainViewModel>();
builder.Services.AddTransient<DetailViewModel>();
// Pages
builder.Services.AddTransient<MainPage>();
builder.Services.AddTransient<DetailPage>();
return builder.Build();
}
Lifetime Guidance
| Lifetime | Use When | Examples |
|---|---|---|
AddSingleton<T> |
Shared state, expensive to create, or app-wide config | Database connection, settings service, HttpClient factory |
AddTransient<T> |
Stateless, lightweight, or per-request usage | ViewModels, pages, API call wrappers |
AddScoped<T> |
Per-scope lifetime (rarely used in MAUI â no built-in scope per page) | Scoped unit-of-work in manually created scopes |
Rule of thumb: Use
AddTransientfor ViewModels and Pages. UseAddSingletonfor services that hold shared state or are expensive to construct. AvoidAddScopedunless you create and manageIServiceScopeinstances yourself.
Constructor Injection (Preferred)
Inject dependencies through the constructor. The DI container resolves them automatically when the type is itself resolved from the container:
public class MainViewModel
{
private readonly IDataService _dataService;
private readonly IApiClient _apiClient;
public MainViewModel(IDataService dataService, IApiClient apiClient)
{
_dataService = dataService;
_apiClient = apiClient;
}
}
ViewModel â Page Pattern
Register both the ViewModel and the Page. Inject the ViewModel into the Page constructor
and assign it as BindingContext:
public partial class MainPage : ContentPage
{
public MainPage(MainViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
}
Automatic Resolution via Shell Navigation
When Pages are registered in the DI container and registered as Shell routes, Shell resolves them (and their dependencies) automatically:
// In MauiProgram.cs
builder.Services.AddTransient<DetailPage>();
builder.Services.AddTransient<DetailViewModel>();
// Route registration (AppShell.xaml.cs or startup)
Routing.RegisterRoute(nameof(DetailPage), typeof(DetailPage));
// Navigation â DI resolves DetailPage and its DetailViewModel
await Shell.Current.GoToAsync(nameof(DetailPage));
Explicit Resolution
When constructor injection is not available, resolve services explicitly:
// From any Element with a Handler
var service = this.Handler.MauiContext.Services.GetService<IDataService>();
IServiceProvider Injection
Inject IServiceProvider when you need to resolve services dynamically:
public class MyService
{
private readonly IServiceProvider _serviceProvider;
public MyService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void DoWork()
{
var api = _serviceProvider.GetRequiredService<IApiClient>();
}
}
Platform-Specific Service Registration
Use preprocessor directives to register platform-specific implementations:
// In MauiProgram.cs
#if ANDROID
builder.Services.AddSingleton<INotificationService, AndroidNotificationService>();
#elif IOS || MACCATALYST
builder.Services.AddSingleton<INotificationService, AppleNotificationService>();
#elif WINDOWS
builder.Services.AddSingleton<INotificationService, WindowsNotificationService>();
#endif
Interface-First Pattern for Testability
Define interfaces for services so implementations can be swapped in tests:
public interface IDataService
{
Task<List<Item>> GetItemsAsync();
}
public class DataService : IDataService
{
public async Task<List<Item>> GetItemsAsync() { /* ... */ }
}
// Register the interface â implementation mapping
builder.Services.AddSingleton<IDataService, DataService>();
In tests, substitute a mock without touching production code:
var services = new ServiceCollection();
services.AddSingleton<IDataService, FakeDataService>();
Gotcha: XAML Resource Parsing vs. DI Timing
XAML resources (styles, resource dictionaries in App.xaml) are parsed before the
App class is fully resolved from the container. If a resource or converter needs a
service, inject IServiceProvider into the App constructor and resolve what you need
in CreateWindow():
public partial class App : Application
{
private readonly IServiceProvider _services;
public App(IServiceProvider services)
{
_services = services;
InitializeComponent(); // XAML resources parse here
}
protected override Window CreateWindow(IActivationState? activationState)
{
// Safe to resolve services here â container is fully built
var mainPage = _services.GetRequiredService<MainPage>();
return new Window(new AppShell());
}
}
Quick Checklist
- Register every Page and ViewModel you want injected in
MauiProgram.cs. - Prefer constructor injection over service-locator calls.
- Use
AddSingletononly for truly shared or expensive services. - Use interfaces for any service you want to mock in tests.
- Use
#ifdirectives for platform-specific implementations. - Resolve late-bound services in
CreateWindow(), not during XAML parse.