Table of Contents

依赖注入

Crystal.Avalonia 使用 Microsoft.Extensions.DependencyInjection 进行依赖注入。本教程介绍 DI 模式与最佳实践。

基本概念

构造函数注入(推荐)

using CommunityToolkit.Mvvm.ComponentModel;

public partial class UserListViewModel : ObservableObject
{
    private readonly IUserService _userService;
    private readonly ILogger _logger;

    // Dependencies are injected through the constructor
    public UserListViewModel(IUserService userService, ILogger logger)
    {
        _userService = userService;
        _logger = logger;
    }
}

属性注入

using CommunityToolkit.Mvvm.ComponentModel;

public partial class SettingsViewModel : ObservableObject
{
    public IConfigService? ConfigService { get; set; }
}

注册服务

在 Module.RegisterServices 中

public class MyModule : IModule
{
    public void RegisterServices(IServiceCollection services)
    {
        // Transient - New instance each time
        services.AddTransient<IUserService, UserService>();

        // Singleton - Same instance shared
        services.AddSingleton<ISettingsService, SettingsService>();

        // Scoped - One instance per scope
        services.AddScoped<IDbContext, AppDbContext>();

        // Instance - Pre-created instance
        services.AddInstance(new AppMetrics());
    }
}

在 App.RegisterServices 中

public override void RegisterServices(IServiceCollection services)
{
    // Register application-wide services
    services.AddSingleton<IAppConfiguration, AppConfiguration>();
    services.AddSingleton<INavigationService, NavigationService>();

    services.AddMvvmTransient<MainView, MainViewModel>();
}

AddMvvm* 将 ViewModel 注册到 DI 并建立 View 映射。Shell:CreateShell<MainWindow, MainView>()。

View/ViewModel 注册

方法 作用
AddMvvmTransient<TView, TViewModel>() AddTransient<TViewModel>() + View ↔ ViewModel 映射
AddMvvmSingleton<TView, TViewModel>() AddSingleton<TViewModel>() + View ↔ ViewModel 映射

TView 不会注册到 DI。View 由 XAML(View-first)或 Activator.CreateInstance(ViewLocator)创建。

服务生命周期

生命周期 适用场景 行为
Transient 轻量、无状态服务 每次新建实例
Singleton 共享状态、配置 始终同一实例
Scoped 按操作的数据(如 DB 上下文) 每个作用域一个实例

示例:选择合适生命周期

public void RegisterServices(IServiceCollection services)
{
    // Transient: New instance for each request
    // Good for: ViewModels, lightweight services
    services.AddTransient<MainViewModel>();
    services.AddTransient<IIdGenerator, GuidIdGenerator>();

    // Singleton: Shared across the app
    // Good for: Configuration, logging, caching
    services.AddSingleton<AppSettings>();
    services.AddSingleton<ILogger, FileLogger>();

    // Scoped: Per operation/scope
    // Good for: Database contexts
    services.AddScoped<IDbContext>(sp => new MyDbContext());
}

高级模式

工厂模式

public interface IViewModelFactory
{
    TViewModel Create<TViewModel>() where TViewModel : class;
}

public class ViewModelFactory : IViewModelFactory
{
    private readonly IServiceProvider _serviceProvider;

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

    public TViewModel Create<TViewModel>() where TViewModel : class
    {
        return _serviceProvider.GetRequiredService<TViewModel>();
    }
}

// Registration
services.AddSingleton<IViewModelFactory, ViewModelFactory>();

// Usage
public partial class DetailViewModel : ObservableObject
{
    private readonly IViewModelFactory _factory;

    public DetailViewModel(IViewModelFactory factory)
    {
        _factory = factory;
    }

    public void OpenItem(ItemViewModel item)
    {
        var vm = _factory.Create<ItemDetailViewModel>();
        vm.Load(item);
    }
}

延迟解析

using CommunityToolkit.Mvvm.ComponentModel;

public partial class MyViewModel : ObservableObject
{
    private readonly Lazy<IHeavyService> _heavyService;

    public MyViewModel(Lazy<IHeavyService> heavyService)
    {
        _heavyService = heavyService;
    }

    public void DoWork()
    {
        // Service is only created when accessed
        var service = _heavyService.Value;
        service.Execute();
    }
}

Options 模式

// Configuration
public class AppSettings
{
    public string ApiBaseUrl { get; set; } = "";
    public int TimeoutSeconds { get; set; } = 30;
}

// Registration
var settings = new AppSettings
{
    ApiBaseUrl = "https://api.example.com",
    TimeoutSeconds = 60
};
services.AddSingleton(settings);

// Usage
public class ApiService : IApiService
{
    public ApiService(AppSettings settings)
    {
        var baseUrl = settings.ApiBaseUrl;
        var timeout = settings.TimeoutSeconds;
    }
}

跨模块共享服务

在主应用中定义服务

// Main App
public override void RegisterServices(IServiceCollection services)
{
    services.AddSingleton<INavigationService, NavigationService>();
}

在模块中使用

public class MyModule : IModule
{
    public void InitializeModule(IServiceProvider serviceProvider)
    {
        // NavigationService was registered in the main app
        var nav = serviceProvider.GetRequiredService<INavigationService>();
        nav.NavigateToHome();
    }
}

使用 DI 进行测试

注册测试替身

public void Setup()
{
    var services = new ServiceCollection();

    // Register test doubles
    services.AddSingleton<IUserService, MockUserService>();
    services.AddSingleton<ILogger, MockLogger>();

    // Register the ViewModel
    services.AddTransient<UserListViewModel>();

    var provider = services.BuildServiceProvider();
    var vm = provider.GetRequiredService<UserListViewModel>();
}

测试 ViewModel

public class UserListViewModelTests
{
    [Fact]
    public void LoadUsers_UpdatesItemsList()
    {
        // Arrange
        var mockService = new MockUserService();
        var vm = new UserListViewModel(mockService);

        // Act
        vm.RefreshCommand.Execute();

        // Assert
        Assert.NotEmpty(vm.Users);
    }
}

常见陷阱

1. 被捕获依赖(Captive Dependency)

// WRONG: DbContext captured by singleton
services.AddSingleton<IService>(sp =>
{
    var dbContext = sp.GetRequiredService<DbContext>(); // ❌
    return new Service(dbContext);
});

// CORRECT: Scoped DbContext
services.AddScoped<MyService>(sp =>
{
    var dbContext = sp.GetRequiredService<DbContext>(); // ✓
    return new Service(dbContext);
});

2. 循环依赖

// WRONG: Circular dependency
public class A
{
    public A(B b) { }
}
public class B
{
    public B(A a) { }  // ❌ Circular!

// CORRECT: Break the cycle with an interface
public interface IA { }
public interface IB { }
public class A : IA
{
    public A(IB b) { }
}

下一步

延伸阅读

工作原理: 架构原理 — 启动流程 — App 与模块服务的注册时机。设计决策 — ViewModel 在 DI 中,View 仅通过映射。