在现代的WPF应用开发中,Prism框架提供了强大的模块化、依赖注入和MVVM支持,而Microsoft.Extensions.DependencyInjection
提供了简洁而功能强大的依赖注入机制。另外很重要的一点是Microsoft.Extensions.*
或者第三方的Nuget基本会提供Microsoft.Extensions.DependencyInjection
, 那么使用起来省心省力提高开发效率是必然的,本文档将详细讲解如何在Prism框架的WPF应用中集成Microsoft.Extensions.DependencyInjection
,并通过具体的代码示例来说明关键点。
1. 项目概述
在本项目中,我们将构建一个使用Prism框架的WPF应用,并集成Microsoft.Extensions.DependencyInjection
进行依赖注入管理。我们的主要目标包括:
- 配置日志记录
- 处理全局未捕获的异常
- 使用DryIoc作为依赖注入容器
- 注册和配置各种服务,包括数据库和自定义服务
2. 初始化与启动
在应用程序启动时,首先需要进行日志记录配置和全局异常处理的设置。
protected override void OnStartup(StartupEventArgs e)
{
var LOG_TEMPLATE = @"[{Timestamp:yyyy-MM-dd hh:mm:ss.fff} {Level:u3}] {Message:lj} {NewLine}{Exception}";
Log.Logger = new LoggerConfiguration()
.MinimumLevel
.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.File("Logs/.log", rollingInterval: RollingInterval.Day, outputTemplate: LOG_TEMPLATE)
.WriteTo.Sink(_routerSink)
.CreateLogger();
try
{
base.OnStartup(e);
// 单实例检查
Process ap = Process.GetCurrentProcess();
if (Process.GetProcessesByName(ap.ProcessName).Length > 1)
{
MessageBox.Show("程序已运行.", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 全局异常处理
ConfigureGlobalExceptionHandlers();
}
catch (Exception ex)
{
Log.Logger.Error(ex.Message);
MessageBox.Show(ex.Message, "应用程序错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
3. 创建主窗口
protected override Window CreateShell()
{
var logEventReceiver = Container.Resolve<LogPageViewModel>() as ILogEventReceiver;
var observableSink = new ObservableSink(logEventReceiver);
_routerSink.AddSink(observableSink);
this.UseHSLAuthorization("****************************");
return Container.Resolve<MainWindow>();
}
- 创建主窗口:从依赖注入容器中解析
MainWindow
实例。
4. 日志配置
日志记录采用Serilog配置,将日志输出到文件,并使用自定义Sink处理日志事件。
var LOG_TEMPLATE = @"[{Timestamp:yyyy-MM-dd hh:mm:ss.fff} {Level:u3}] {Message:lj} {NewLine}{Exception}";
Log.Logger = new LoggerConfiguration()
.MinimumLevel
.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.File("Logs/.log", rollingInterval: RollingInterval.Day, outputTemplate: LOG_TEMPLATE)
.WriteTo.Sink(_routerSink)
.CreateLogger();
5. 异常处理
为了提高应用的稳定性,我们设置了全局的未处理异常处理程序,包括UI线程、非UI线程和任务线程。
private void ConfigureGlobalExceptionHandlers()
{
// 捕获UI线程未处理的异常
DispatcherUnhandledException += (sender, ex) =>
{
Log.Logger.Error(ex.Exception.Message);
ex.Handled = true;
};
// 捕获非UI线程未处理的异常
AppDomain.CurrentDomain.UnhandledException += (sender, ex) =>
{
Exception exception = (Exception)ex.ExceptionObject;
if (exception != null)
{
Log.Logger.Error(exception.Message);
}
};
// 捕获Task线程中未处理的异常
TaskScheduler.UnobservedTaskException += (sender, ex) =>
{
Log.Logger.Error(ex.Exception.Message);
};
}
6. 依赖注入容器配置
使用DryIoc作为依赖注入容器,并集成Microsoft.Extensions.DependencyInjection
服务。
protected override IContainerExtension CreateContainerExtension()
{
var services = new ServiceCollection();
ConfigureServices(services);
var container = new DryIoc.Container(CreateContainerRules());
container.WithDependencyInjectionAdapter(services);
return new DryIocContainerExtension(container);
}
7. 服务注册与配置
在ConfigureServices
方法中,我们注册了各种服务,包括日志服务、数据库服务和自定义服务。
private void ConfigureServices(IServiceCollection services)
{
services.AddLogging(builder => builder.AddSerilog(dispose: true));
services.AddScoped<IAsyncInterceptor, ExceptionInterceptor>();
services.AddDefaultProxyGenerator()
.AddTransientWithAsyncInterceptor<EquipmentControlViewModel, ExceptionInterceptorAsync>();
services.AddTransientWithAsyncInterceptor<AbsoluteLocationViewModel, ExceptionInterceptorAsync>();
var configurationBuilder = new ConfigurationManager()
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddJsonFile("appsettings.json", true, true);
var configuration = configurationBuilder.Build();
services.AddSingleton<IConfiguration>(configuration);
// ViewModel
services.AddScoped<LogPageViewModel>();
// SqlSugar
ISqlSugarClient sugar = new SqlSugarScope(
new ConnectionConfig
{
DbType = SqlSugar.DbType.Sqlite,
ConnectionString = configuration.GetConnectionString("Sqlite"),
InitKeyType = InitKeyType.Attribute,
IsAutoCloseConnection = true
}
);
sugar.DbMaintenance.CreateDatabase(AppDomain.CurrentDomain.BaseDirectory);
sugar.CodeFirst.InitTables<Alarm>();
sugar.CodeFirst.InitTables<Warning>();
sugar.CodeFirst.InitTables<IO>();
sugar.CodeFirst.InitTables<EquiControl>();
sugar.CodeFirst.InitTables<EquiControlGroup>();
sugar.CodeFirst.InitTables<JogControl>();
sugar.CodeFirst.InitTables<JogControlGroup>();
sugar.CodeFirst.InitTables<RealVal>();
sugar.CodeFirst.InitTables<RealValGroup>();
sugar.CodeFirst.InitTables<DIntVal>();
sugar.CodeFirst.InitTables<DIntValGroup>();
services.AddSingleton<ISqlSugarClient>(sugar);
// Repositories
services.AddTransient<IIORepository, IORepository>();
services.AddTransient<IAlarmReository, AlarmRepository>();
services.AddTransient<IWarningRepository, WarningRepository>();
services.AddTransient<IEquiControlGroupRepository, EquiControlGroupRepository>();
services.AddTransient<IJogControlGroupRepository, JogControlRepository>();
services.AddTransient<IRealValGroupRepository, RealValGroupRepository>();
services.AddTransient<IDIntValGroupRepository, DIntValGroupRepository>();
// HSLDIExtensions
var deploymentType = configuration.GetSection("DeploymentType").Get<string>();
if (string.IsNullOrWhiteSpace(deploymentType) || deploymentType == "模拟")
{
//services.AddScoped<IPLCService, FakePLCService>();
}
else if (deploymentType == "生产机台")
{
services.AddHSL(options =>
{
options.UseS7Net("控制器", configuration);
});
services.AddSingleton<IPLCService, PLCService>();
}
}
8. 其他配置与功能
除了核心功能的配置外,Prism还提供了ViewModel定位器、模块目录、区域适配器映射等功能,以增强应用的模块化和可维护性。
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
}
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<PubSubEventModule>();
}
protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings)
{
base.ConfigureRegionAdapterMappings(regionAdapterMappings);
}
protected override void ConfigureDefaultRegionBehaviors(IRegionBehaviorFactory regionBehaviors)
{
base.ConfigureDefaultRegionBehaviors(regionBehaviors);
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterDialog<HintMessageDialog>(nameof(HintMessageDialog));
// Register Region
containerRegistry.RegisterForNavigation<HomePage>();
containerRegistry.RegisterForNavigation<SpeedPage>();
containerRegistry.RegisterForNavigation<AlarmPage>();
containerRegistry.RegisterForNavigation<IOPage>();
containerRegistry.RegisterForNavigation<CompositePage>();
containerRegistry.RegisterForNavigation<LogPage>();
containerRegistry.RegisterForNavigation<ViusalTestView>();
}
9. Prism与DryIoc集成中的规则配置详解
在集成Prism与DryIoc的过程中,我们需要配置DryIoc的规则以满足Prism框架的需求。下面我们将详细解析相关代码,并说明其中的一些关键点,特别是关于DryIoc的版本依赖和规则配置的细节。
依赖版本
在使用Prism和DryIoc进行集成时,需要注意以下依赖版本:
Prism.DryIoc
对 DryIoc 的依赖是 >= 4.7DryIoc.Microsoft.DependencyInjection
对 DryIoc 的依赖是 >= 5.40
这意味着在DryIoc的新版本中,某些扩展方法已经被移除或替换,例如WithoutFastExpressionCompiler()
。
默认规则配置
首先,我们定义了一个静态属性DefaultRules
,用于配置DryIoc的默认规则,代码参考PrismApplication
中的DryIocContainerExtension.DefaultRules
实现,注释掉不存在的扩展方法即可。
/// <summary>
/// Gets the Default DryIoc Container Rules used by Prism
/// </summary>
public static Rules DefaultRules =>
Rules.Default
.WithConcreteTypeDynamicRegistrations(reuse: Reuse.Transient)
.With(Made.Of(FactoryMethod.ConstructorWithResolvableArguments))
.WithFuncAndLazyWithoutRegistration()
.WithTrackingDisposableTransients()
//.WithoutFastExpressionCompiler()
.WithFactorySelector(Rules.SelectLastRegisteredFactory());
规则详解:
- WithConcreteTypeDynamicRegistrations:
- 配置DryIoc以动态注册具体类型,指定使用瞬态(Transient)生命周期。
- With(Made.Of(FactoryMethod.ConstructorWithResolvableArguments)):
- 指定在解析类型时使用可解析参数的构造函数。
- WithFuncAndLazyWithoutRegistration:
- 允许使用未注册的Func和Lazy类型。
- WithTrackingDisposableTransients:
- 跟踪并处理瞬态(Transient)生命周期的可释放对象。
- WithFactorySelector(Rules.SelectLastRegisteredFactory):
- 使用最后注册的工厂选择器。
注意:在DryIoc的新版本中,WithoutFastExpressionCompiler()
已被移除,因此该方法被注释掉。
创建规则
接下来,我们通过覆盖CreateContainerRules
方法来创建并返回默认规则。
/// <summary>
/// Create <see cref="Rules" /> to alter behavior of <see cref="IContainer" />
/// </summary>
/// <returns>An instance of <see cref="Rules" /></returns>
protected override Rules CreateContainerRules() => DefaultRules;
创建容器扩展
然后,我们通过覆盖CreateContainerExtension
方法来创建DryIoc容器,并集成Microsoft的依赖注入服务。
protected override IContainerExtension CreateContainerExtension()
{
var services = new ServiceCollection();
ConfigureServices(services);
var container = new DryIoc.Container(CreateContainerRules());
container.WithDependencyInjectionAdapter(services);
return new DryIocContainerExtension(container);
}
步骤详解:
- ConfigureServices(services):
- 配置Microsoft依赖注入服务集合。
- CreateContainerRules():
- 创建并获取容器规则。
- new DryIoc.Container(CreateContainerRules()):
- 使用自定义规则创建DryIoc容器实例。
- container.WithDependencyInjectionAdapter(services):
- 将Microsoft依赖注入服务集合适配到DryIoc容器中。
- return new DryIocContainerExtension(container):
- 返回DryIoc容器扩展实例。
通过上述配置,我们实现了Prism框架与DryIoc容器的集成。在DryIoc的新版本中,某些方法如WithoutFastExpressionCompiler()
已经被移除,因此在使用这些版本时,需要确保代码适配最新的API变化。通过合理配置DryIoc的规则和容器扩展,可以充分发挥Prism框架的模块化和依赖注入功能,提高应用的可维护性和扩展性。