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
使用 Visual Studio 创建新应用教程 - WPF .NET | Microsoft Learn
https://github.com/HeBianGu
HeBianGu的个人空间-HeBianGu个人主页-哔哩哔哩视频