maui-dependency-injection

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Dependency Injection in .NET MAUI

.NET MAUI中的依赖注入

Service Registration in MauiProgram.cs

MauiProgram.cs中的服务注册

Register services on
builder.Services
inside
CreateMauiApp()
:
csharp
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();
}
CreateMauiApp()
方法内的
builder.Services
上注册服务:
csharp
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

生命周期指南

LifetimeUse WhenExamples
AddSingleton<T>
Shared state, expensive to create, or app-wide configDatabase connection, settings service, HttpClient factory
AddTransient<T>
Stateless, lightweight, or per-request usageViewModels, 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
AddTransient
for ViewModels and Pages. Use
AddSingleton
for services that hold shared state or are expensive to construct. Avoid
AddScoped
unless you create and manage
IServiceScope
instances yourself.
生命周期使用场景示例
AddSingleton<T>
共享状态、创建成本高或应用全局配置数据库连接、设置服务、HttpClient工厂
AddTransient<T>
无状态、轻量级或每次请求使用ViewModel、页面、API调用包装器
AddScoped<T>
每个作用域的生命周期(在MAUI中很少使用——没有内置的每页作用域)手动创建作用域中的作用域工作单元
经验法则: 对ViewModel和Page使用
AddTransient
。对持有共享状态或构造成本高的服务使用
AddSingleton
。除非您自己创建并管理
IServiceScope
实例,否则避免使用
AddScoped

Constructor Injection (Preferred)

构造函数注入(推荐方式)

Inject dependencies through the constructor. The DI container resolves them automatically when the type is itself resolved from the container:
csharp
public class MainViewModel
{
    private readonly IDataService _dataService;
    private readonly IApiClient _apiClient;

    public MainViewModel(IDataService dataService, IApiClient apiClient)
    {
        _dataService = dataService;
        _apiClient = apiClient;
    }
}
通过构造函数注入依赖项。当从容器中解析类型本身时,DI容器会自动解析这些依赖项:
csharp
public class MainViewModel
{
    private readonly IDataService _dataService;
    private readonly IApiClient _apiClient;

    public MainViewModel(IDataService dataService, IApiClient apiClient)
    {
        _dataService = dataService;
        _apiClient = apiClient;
    }
}

ViewModel → Page Pattern

ViewModel → Page 模式

Register both the ViewModel and the Page. Inject the ViewModel into the Page constructor and assign it as
BindingContext
:
csharp
public partial class MainPage : ContentPage
{
    public MainPage(MainViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}
同时注册ViewModel和Page。将ViewModel注入到Page的构造函数中,并将其赋值为
BindingContext
csharp
public partial class MainPage : ContentPage
{
    public MainPage(MainViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}

Automatic Resolution via Shell Navigation

通过Shell导航自动解析

When Pages are registered in the DI container and registered as Shell routes, Shell resolves them (and their dependencies) automatically:
csharp
// 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));
当Page在DI容器中注册并且注册为Shell路由时,Shell会自动解析它们(及其依赖项):
csharp
// In MauiProgram.cs
builder.Services.AddTransient<DetailPage>();
builder.Services.AddTransient<DetailViewModel>();

// 路由注册(AppShell.xaml.cs或启动时)
Routing.RegisterRoute(nameof(DetailPage), typeof(DetailPage));

// 导航——DI会解析DetailPage及其DetailViewModel
await Shell.Current.GoToAsync(nameof(DetailPage));

Explicit Resolution

显式解析

When constructor injection is not available, resolve services explicitly:
csharp
// From any Element with a Handler
var service = this.Handler.MauiContext.Services.GetService<IDataService>();
当无法使用构造函数注入时,显式解析服务:
csharp
// 从任何带有Handler的Element中获取
var service = this.Handler.MauiContext.Services.GetService<IDataService>();

IServiceProvider Injection

注入IServiceProvider

Inject
IServiceProvider
when you need to resolve services dynamically:
csharp
public class MyService
{
    private readonly IServiceProvider _serviceProvider;

    public MyService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void DoWork()
    {
        var api = _serviceProvider.GetRequiredService<IApiClient>();
    }
}
当您需要动态解析服务时,注入
IServiceProvider
csharp
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:
csharp
// 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
使用预处理器指令注册特定平台的实现:
csharp
// 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:
csharp
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:
csharp
var services = new ServiceCollection();
services.AddSingleton<IDataService, FakeDataService>();
为服务定义接口,以便在测试中替换实现:
csharp
public interface IDataService
{
    Task<List<Item>> GetItemsAsync();
}

public class DataService : IDataService
{
    public async Task<List<Item>> GetItemsAsync() { /* ... */ }
}

// 注册接口→实现的映射
builder.Services.AddSingleton<IDataService, DataService>();
在测试中,无需修改生产代码即可替换为模拟实现:
csharp
var services = new ServiceCollection();
services.AddSingleton<IDataService, FakeDataService>();

Gotcha: XAML Resource Parsing vs. DI Timing

注意事项:XAML资源解析与DI时机

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()
:
csharp
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());
    }
}
XAML资源(样式、
App.xaml
中的资源字典)在容器完全解析
App
之前就会被解析。如果资源或转换器需要服务,请将
IServiceProvider
注入到
App
的构造函数中,并在
CreateWindow()
中解析所需服务:
csharp
public partial class App : Application
{
    private readonly IServiceProvider _services;

    public App(IServiceProvider services)
    {
        _services = services;
        InitializeComponent(); // XAML资源在此处解析
    }

    protected override Window CreateWindow(IActivationState? activationState)
    {
        // 在此处解析延迟绑定的服务是安全的——容器已完全构建
        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
    AddSingleton
    only for truly shared or expensive services.
  • Use interfaces for any service you want to mock in tests.
  • Use
    #if
    directives for platform-specific implementations.
  • Resolve late-bound services in
    CreateWindow()
    , not during XAML parse.
  • MauiProgram.cs
    中注册所有需要注入的Page和ViewModel。
  • 优先使用构造函数注入而非服务定位器调用。
  • 仅对真正共享或创建成本高的服务使用
    AddSingleton
  • 对任何需要在测试中模拟的服务使用接口。
  • 使用
    #if
    指令处理特定平台的实现。
  • CreateWindow()
    中解析延迟绑定的服务,而非在XAML解析期间。