maui-dependency-injection
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDependency Injection in .NET MAUI
.NET MAUI中的依赖注入
Service Registration in MauiProgram.cs
MauiProgram.cs中的服务注册
Register services on inside :
builder.ServicesCreateMauiApp()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.Servicescsharp
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 |
|---|---|---|
| Shared state, expensive to create, or app-wide config | Database connection, settings service, HttpClient factory |
| Stateless, lightweight, or per-request usage | ViewModels, pages, API call wrappers |
| Per-scope lifetime (rarely used in MAUI — no built-in scope per page) | Scoped unit-of-work in manually created scopes |
Rule of thumb: Usefor ViewModels and Pages. UseAddTransientfor services that hold shared state or are expensive to construct. AvoidAddSingletonunless you create and manageAddScopedinstances yourself.IServiceScope
| 生命周期 | 使用场景 | 示例 |
|---|---|---|
| 共享状态、创建成本高或应用全局配置 | 数据库连接、设置服务、HttpClient工厂 |
| 无状态、轻量级或每次请求使用 | ViewModel、页面、API调用包装器 |
| 每个作用域的生命周期(在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 :
BindingContextcsharp
public partial class MainPage : ContentPage
{
public MainPage(MainViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
}同时注册ViewModel和Page。将ViewModel注入到Page的构造函数中,并将其赋值为:
BindingContextcsharp
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 when you need to resolve services dynamically:
IServiceProvidercsharp
public class MyService
{
private readonly IServiceProvider _serviceProvider;
public MyService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void DoWork()
{
var api = _serviceProvider.GetRequiredService<IApiClient>();
}
}当您需要动态解析服务时,注入:
IServiceProvidercsharp
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>();
#endifInterface-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 ) are parsed before the
class is fully resolved from the container. If a resource or converter needs a
service, inject into the constructor and resolve what you need
in :
App.xamlAppIServiceProviderAppCreateWindow()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.xamlAppIServiceProviderAppCreateWindow()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 only for truly shared or expensive services.
AddSingleton - Use interfaces for any service you want to mock in tests.
- Use directives for platform-specific implementations.
#if - Resolve late-bound services in , not during XAML parse.
CreateWindow()
- 在中注册所有需要注入的Page和ViewModel。
MauiProgram.cs - 优先使用构造函数注入而非服务定位器调用。
- 仅对真正共享或创建成本高的服务使用。
AddSingleton - 对任何需要在测试中模拟的服务使用接口。
- 使用指令处理特定平台的实现。
#if - 在中解析延迟绑定的服务,而非在XAML解析期间。
CreateWindow()