【笔记】在WPF App.cs中结合 IServiceCollection 进行 IOC 依赖注入

WPF 中结合 IServiceCollection 进行 IOC 依赖注入

一、基础集成方案

1.1 创建服务配置类

csharp 复制代码
// App.xaml.cs 或 Program.cs
public partial class App : Application
{
    private IServiceProvider _serviceProvider;
    
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        
        // 创建服务集合
        var services = new ServiceCollection();
        ConfigureServices(services);
        
        // 构建服务提供者
        _serviceProvider = services.BuildServiceProvider();
        
        // 设置主窗口
        var mainWindow = _serviceProvider.GetRequiredService<MainWindow>();
        mainWindow.Show();
    }
    
    private void ConfigureServices(IServiceCollection services)
    {
        // 注册窗口和视图
        services.AddSingleton<MainWindow>();
        services.AddTransient<SettingsWindow>();
        services.AddTransient<UserDetailsWindow>();
        
        // 注册视图模型(ViewModel-First 模式)
        services.AddSingleton<MainViewModel>();
        services.AddTransient<SettingsViewModel>();
        services.AddTransient<UserViewModel>();
        
        // 注册业务服务
        services.AddScoped<IUserService, UserService>();
        services.AddScoped<IProductService, ProductService>();
        services.AddScoped<IOrderService, OrderService>();
        
        // 注册数据访问层
        services.AddScoped<IUserRepository, UserRepository>();
        services.AddScoped<IProductRepository, ProductRepository>();
        
        // 注册基础设施
        services.AddSingleton<ILogger, FileLogger>();
        services.AddSingleton<IConfigurationService, AppConfigurationService>();
        services.AddSingleton<INavigationService, NavigationService>();
        
        // 注册HTTP客户端
        services.AddHttpClient<IApiService, ApiService>(client =>
        {
            client.BaseAddress = new Uri("https://api.example.com/");
            client.Timeout = TimeSpan.FromSeconds(30);
        });
    }
}

二、MVVM 模式完整集成

2.1 安装必要 NuGet 包

xml 复制代码
<!-- 项目文件 (.csproj) -->
<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
  <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
  <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
</ItemGroup>

2.2 使用 IHost(推荐方式)

csharp 复制代码
// Program.cs (使用新的项目模板)
public class Program
{
    [STAThread]
    public static void Main()
    {
        var host = Host.CreateDefaultBuilder()
            .ConfigureServices((context, services) =>
            {
                ConfigureServices(services);
                ConfigureViewModels(services);
                ConfigureViews(services);
            })
            .Build();
        
        var app = host.Services.GetRequiredService<App>();
        app.InitializeComponent();
        app.Run();
    }
    
    private static void ConfigureServices(IServiceCollection services)
    {
        // 注册应用
        services.AddSingleton<App>();
        
        // 配置系统
        services.Configure<AppSettings>(options =>
        {
            options.ConnectionString = "Server=.;Database=AppDb;...";
            options.ApiEndpoint = "https://api.example.com";
        });
        
        // 数据库上下文
        services.AddDbContext<AppDbContext>(options =>
            options.UseSqlServer("ConnectionString"));
        
        // 缓存
        services.AddMemoryCache();
        
        // 后台服务
        services.AddHostedService<BackgroundTaskService>();
    }
    
    private static void ConfigureViewModels(IServiceCollection services)
    {
        // 使用特性自动注册
        services.AddViewModels();
        
        // 或者手动注册
        services.AddSingleton<MainViewModel>();
        services.AddTransient<LoginViewModel>();
        services.AddTransient<DashboardViewModel>();
    }
    
    private static void ConfigureViews(IServiceCollection services)
    {
        services.AddSingleton<MainWindow>();
        services.AddTransient<LoginWindow>();
        services.AddTransient<DashboardWindow>();
    }
}

// App.xaml.cs
public partial class App : Application
{
    private readonly IHost _host;
    
    public App()
    {
        _host = Host.CreateDefaultBuilder()
            .ConfigureServices(ConfigureServices)
            .Build();
    }
    
    private void ConfigureServices(HostBuilderContext context, IServiceCollection services)
    {
        // 配置服务
    }
    
    protected override async void OnStartup(StartupEventArgs e)
    {
        await _host.StartAsync();
        
        var mainWindow = _host.Services.GetRequiredService<MainWindow>();
        mainWindow.Show();
        
        base.OnStartup(e);
    }
    
    protected override async void OnExit(ExitEventArgs e)
    {
        using (_host)
        {
            await _host.StopAsync(TimeSpan.FromSeconds(5));
        }
        base.OnExit(e);
    }
}

三、ViewModel 自动注册扩展

3.1 创建自动注册扩展

csharp 复制代码
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddViewModels(this IServiceCollection services)
    {
        var viewModelTypes = Assembly.GetExecutingAssembly()
            .GetTypes()
            .Where(t => t.IsClass && 
                       !t.IsAbstract && 
                       t.Name.EndsWith("ViewModel"));
        
        foreach (var type in viewModelTypes)
        {
            // 根据名称确定生命周期
            if (type.Name.StartsWith("Main") || type.Name.StartsWith("Shell"))
            {
                services.AddSingleton(type);
            }
            else
            {
                services.AddTransient(type);
            }
        }
        
        return services;
    }
    
    public static IServiceCollection AddPages(this IServiceCollection services)
    {
        var pageTypes = Assembly.GetExecutingAssembly()
            .GetTypes()
            .Where(t => t.IsClass && 
                       !t.IsAbstract && 
                       typeof(Page).IsAssignableFrom(t));
        
        foreach (var type in pageTypes)
        {
            services.AddTransient(type);
        }
        
        return services;
    }
    
    public static IServiceCollection AddWindows(this IServiceCollection services)
    {
        var windowTypes = Assembly.GetExecutingAssembly()
            .GetTypes()
            .Where(t => t.IsClass && 
                       !t.IsAbstract && 
                       typeof(Window).IsAssignableFrom(t));
        
        foreach (var type in windowTypes)
        {
            if (type.Name == "MainWindow")
                services.AddSingleton(type);
            else
                services.AddTransient(type);
        }
        
        return services;
    }
}

四、依赖注入在实际组件中的应用

4.1 在 ViewModel 中使用依赖注入

csharp 复制代码
// MainViewModel.cs
public class MainViewModel : ObservableObject
{
    private readonly IUserService _userService;
    private readonly INavigationService _navigationService;
    private readonly ILogger _logger;
    private readonly IMediator _mediator;
    
    public MainViewModel(
        IUserService userService,
        INavigationService navigationService,
        ILogger logger,
        IMediator mediator)
    {
        _userService = userService;
        _navigationService = navigationService;
        _logger = logger;
        _mediator = mediator;
        
        InitializeCommands();
        LoadData();
    }
    
    private void InitializeCommands()
    {
        NavigateCommand = new RelayCommand<string>(Navigate);
        SaveCommand = new AsyncRelayCommand(SaveAsync);
        DeleteCommand = new RelayCommand<User>(DeleteUser);
    }
    
    private async void LoadData()
    {
        try
        {
            IsLoading = true;
            Users = await _userService.GetAllUsersAsync();
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "加载用户数据失败");
            await _mediator.Publish(new ErrorNotification("加载失败"));
        }
        finally
        {
            IsLoading = false;
        }
    }
    
    // 命令和属性...
    public ICommand NavigateCommand { get; private set; }
    public IAsyncRelayCommand SaveCommand { get; private set; }
    public ICommand DeleteCommand { get; private set; }
}

4.2 在 UserControl 中使用服务定位器模式

xml 复制代码
<!-- MainWindow.xaml -->
<Window x:Class="MyApp.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">
    
    <Window.Resources>
        <vm:ViewModelLocator x:Key="Locator"/>
    </Window.Resources>
    
    <Grid>
        <ContentControl Content="{Binding CurrentPage}">
            <ContentControl.Resources>
                <!-- 数据模板映射 -->
                <DataTemplate DataType="{x:Type vm:LoginViewModel}">
                    <views:LoginView/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type vm:DashboardViewModel}">
                    <views:DashboardView/>
                </DataTemplate>
            </ContentControl.Resources>
        </ContentControl>
    </Grid>
</Window>

五、视图模型定位器模式

5.1 实现 ViewModelLocator

csharp 复制代码
public class ViewModelLocator
{
    private readonly IServiceProvider _serviceProvider;
    
    public ViewModelLocator()
    {
        // 在 App.xaml.cs 中设置 ServiceProvider
        _serviceProvider = ((App)Application.Current).ServiceProvider;
    }
    
    // 视图模型属性
    public MainViewModel MainViewModel => 
        _serviceProvider.GetRequiredService<MainViewModel>();
    
    public SettingsViewModel SettingsViewModel => 
        _serviceProvider.GetRequiredService<SettingsViewModel>();
    
    public UserViewModel UserViewModel => 
        _serviceProvider.GetRequiredService<UserViewModel>();
    
    // 动态解析
    public object GetViewModel(Type viewModelType)
    {
        return _serviceProvider.GetService(viewModelType);
    }
    
    public T GetViewModel<T>() where T : class
    {
        return _serviceProvider.GetRequiredService<T>();
    }
}

六、窗口和对话框的依赖注入

6.1 带参数的窗口创建

csharp 复制代码
public interface IWindowService
{
    void ShowMainWindow();
    bool? ShowDialog<TWindow>() where TWindow : Window;
    bool? ShowDialog<TWindow, TViewModel>(TViewModel viewModel) 
        where TWindow : Window;
    void ShowWindow<TWindow>() where TWindow : Window;
}

public class WindowService : IWindowService
{
    private readonly IServiceProvider _serviceProvider;
    
    public WindowService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    
    public bool? ShowDialog<TWindow>() where TWindow : Window
    {
        var window = _serviceProvider.GetRequiredService<TWindow>();
        return window.ShowDialog();
    }
    
    public bool? ShowDialog<TWindow, TViewModel>(TViewModel viewModel) 
        where TWindow : Window
    {
        var window = _serviceProvider.GetRequiredService<TWindow>();
        
        if (window.DataContext is IRequireViewModel<TViewModel> requireVm)
        {
            requireVm.ViewModel = viewModel;
        }
        else
        {
            window.DataContext = viewModel;
        }
        
        return window.ShowDialog();
    }
    
    public void ShowWindow<TWindow>() where TWindow : Window
    {
        var window = _serviceProvider.GetRequiredService<TWindow>();
        window.Show();
    }
}

// 在 ViewModel 中使用
public class MainViewModel
{
    private readonly IWindowService _windowService;
    
    public MainViewModel(IWindowService windowService)
    {
        _windowService = windowService;
        OpenSettingsCommand = new RelayCommand(OpenSettings);
    }
    
    private void OpenSettings()
    {
        _windowService.ShowDialog<SettingsWindow>();
    }
    
    public ICommand OpenSettingsCommand { get; }
}

七、配置管理和选项模式

7.1 AppSettings 配置

csharp 复制代码
public class AppSettings
{
    public string ConnectionString { get; set; }
    public string ApiBaseUrl { get; set; }
    public int PageSize { get; set; } = 20;
    public TimeSpan CacheDuration { get; set; } = TimeSpan.FromMinutes(30);
}

// 注册配置
services.Configure<AppSettings>(options =>
{
    options.ConnectionString = Configuration.GetConnectionString("Default");
    options.ApiBaseUrl = Configuration["Api:BaseUrl"];
    options.PageSize = int.Parse(Configuration["Settings:PageSize"]);
});

// 在服务中使用
public class ApiService : IApiService
{
    private readonly IOptions<AppSettings> _settings;
    private readonly HttpClient _httpClient;
    
    public ApiService(IOptions<AppSettings> settings, HttpClient httpClient)
    {
        _settings = settings;
        _httpClient = httpClient;
        _httpClient.BaseAddress = new Uri(_settings.Value.ApiBaseUrl);
    }
}

八、完整示例:WPF + IOC 项目结构

8.1 项目结构

复制代码
MyWpfApp/
├── Services/
│   ├── Interfaces/
│   │   ├── IUserService.cs
│   │   └── INavigationService.cs
│   └── Implementations/
│       ├── UserService.cs
│       └── NavigationService.cs
├── ViewModels/
│   ├── Base/
│   │   └── ObservableObject.cs
│   ├── MainViewModel.cs
│   └── UserViewModel.cs
├── Views/
│   ├── MainWindow.xaml
│   └── UserView.xaml
├── Extensions/
│   └── ServiceCollectionExtensions.cs
└── App.xaml.cs

8.2 App.xaml.cs 完整示例

csharp 复制代码
public partial class App : Application
{
    public IServiceProvider ServiceProvider { get; private set; }
    public IConfiguration Configuration { get; private set; }
    
    protected override void OnStartup(StartupEventArgs e)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
            .AddEnvironmentVariables();
        
        Configuration = builder.Build();
        
        var serviceCollection = new ServiceCollection();
        ConfigureServices(serviceCollection);
        
        ServiceProvider = serviceCollection.BuildServiceProvider();
        
        // 设置主窗口
        var mainWindow = ServiceProvider.GetRequiredService<MainWindow>();
        mainWindow.Show();
    }
    
    private void ConfigureServices(IServiceCollection services)
    {
        // 配置
        services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
        
        // 日志
        services.AddLogging(configure => 
            configure.AddConsole().AddDebug().SetMinimumLevel(LogLevel.Debug));
        
        // 视图模型
        services.AddSingleton<MainViewModel>();
        services.AddTransient<UserViewModel>();
        services.AddTransient<SettingsViewModel>();
        
        // 窗口
        services.AddSingleton<MainWindow>();
        services.AddTransient<UserWindow>();
        services.AddTransient<SettingsWindow>();
        
        // 服务
        services.AddSingleton<INavigationService, NavigationService>();
        services.AddSingleton<IEventAggregator, EventAggregator>();
        services.AddScoped<IUserService, UserService>();
        services.AddScoped<IProductService, ProductService>();
        
        // 数据库
        services.AddDbContext<AppDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("Default")));
        
        // HTTP客户端
        services.AddHttpClient("ApiClient", client =>
        {
            client.BaseAddress = new Uri(Configuration["Api:BaseUrl"]);
            client.DefaultRequestHeaders.Add("Accept", "application/json");
        });
        
        // 后台服务
        services.AddHostedService<BackgroundWorkerService>();
        
        // 工具服务
        services.AddSingleton<IDialogService, DialogService>();
        services.AddSingleton<IFileService, FileService>();
        
        // 缓存
        services.AddMemoryCache();
        services.AddDistributedMemoryCache();
        
        // 验证
        services.AddScoped<IAuthService, AuthService>();
    }
}

九、实用技巧和最佳实践

9.1 生命周期管理

csharp 复制代码
// 在需要时手动创建作用域
public class MainViewModel
{
    private readonly IServiceScopeFactory _scopeFactory;
    
    public MainViewModel(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }
    
    public async Task ProcessDataAsync()
    {
        // 为长时间操作创建独立的作用域
        using var scope = _scopeFactory.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var repository = scope.ServiceProvider.GetRequiredService<IUserRepository>();
        
        // 操作完成后自动释放资源
        var users = await repository.GetAllAsync();
        // ...
    }
}

9.2 延迟加载服务

csharp 复制代码
public class LazyService<T> : Lazy<T> where T : class
{
    public LazyService(IServiceProvider serviceProvider)
        : base(() => serviceProvider.GetRequiredService<T>())
    {
    }
}

// 在 ViewModel 中使用
public class ComplexViewModel
{
    private readonly LazyService<IExpensiveService> _expensiveService;
    
    public ComplexViewModel(LazyService<IExpensiveService> expensiveService)
    {
        _expensiveService = expensiveService;
    }
    
    public void DoExpensiveOperation()
    {
        if (_expensiveService.IsValueCreated == false)
        {
            // 第一次使用时才创建
            var service = _expensiveService.Value;
            // ...
        }
    }
}

9.3 注册泛型服务

csharp 复制代码
public interface IRepository<T> where T : class
{
    Task<T> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
}

public class Repository<T> : IRepository<T> where T : class
{
    private readonly AppDbContext _context;
    
    public Repository(AppDbContext context)
    {
        _context = context;
    }
    
    // 实现...
}

// 注册所有实体类型的 Repository
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));

十、调试和问题排查

10.1 依赖注入验证

csharp 复制代码
// 在开发环境中验证服务注册
public static class ServiceCollectionExtensions
{
    public static void ValidateServiceRegistrations(this IServiceCollection services)
    {
        var serviceProvider = services.BuildServiceProvider();
        
        foreach (var serviceDescriptor in services)
        {
            try
            {
                var service = serviceProvider.GetService(serviceDescriptor.ServiceType);
                if (service == null && serviceDescriptor.Lifetime != ServiceLifetime.Transient)
                {
                    Debug.WriteLine($"Warning: Service {serviceDescriptor.ServiceType.Name} could not be resolved");
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Error resolving {serviceDescriptor.ServiceType.Name}: {ex.Message}");
            }
        }
    }
}

// 在 ConfigureServices 后调用
services.ValidateServiceRegistrations();

这种集成方式让 WPF 应用获得了现代 .NET 应用的所有优势:

  • 可测试性:易于单元测试和集成测试
  • 松耦合:组件间依赖清晰
  • 可维护性:配置集中管理
  • 现代化:使用 .NET 最新的依赖注入框架
  • 灵活性:轻松切换实现和配置

了解更多

Microsoft.Extensions.DependencyInjection

System.Windows.Controls 命名空间 | Microsoft Learn

控件库 - WPF .NET Framework | Microsoft Learn

WPF 介绍 | Microsoft Learn

使用 Visual Studio 创建新应用教程 - WPF .NET | Microsoft Learn
https://github.com/HeBianGu

HeBianGu的个人空间-HeBianGu个人主页-哔哩哔哩视频

GitHub - HeBianGu/WPF-Control: WPF轻量控件和皮肤库

GitHub - HeBianGu/WPF-ControlBase: Wpf封装的自定义控件资源库

相关推荐
kkkkkkkkl244 小时前
从「知道死锁」到「真正理解死锁」:一次 MySQL 锁机制的学习记录
数据库·mysql
柯南二号4 小时前
【后端】【Java】《Spring Boot 统一接口耗时统计实践:基于 HandlerInterceptor 的工程级方案》
java·开发语言·数据库
m0_740043734 小时前
SpringBoot03-Mybatis框架入门
java·数据库·spring boot·sql·spring·mybatis
colus_SEU4 小时前
【计算机网络笔记】第五章 网络层的控制平面
笔记·计算机网络
Logic1014 小时前
《数据库运维》 郭文明 实验2 MySQL数据库对象管理核心操作与思路解析
运维·数据库·mysql·学习笔记·计算机网络技术·形考作业·国家开放大学
蒙奇D索大4 小时前
【数据结构】考研408 | B树收官:插入与删除的平衡艺术——分裂、合并与借位
数据结构·笔记·b树·考研·改行学it
一 乐4 小时前
心理健康管理|基于springboot + vue心理健康管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
yuguo.im4 小时前
SQL 分析函数 `PERCENTILE_CONT` 的兼容性与深度解析
数据库·sql