Prism框架与Microsoft.Extensions.DependencyInjection的集成使用笔记

在现代的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.7
  • DryIoc.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());

规则详解

  1. WithConcreteTypeDynamicRegistrations:
    • 配置DryIoc以动态注册具体类型,指定使用瞬态(Transient)生命周期。
  2. With(Made.Of(FactoryMethod.ConstructorWithResolvableArguments)):
    • 指定在解析类型时使用可解析参数的构造函数。
  3. WithFuncAndLazyWithoutRegistration:
    • 允许使用未注册的Func和Lazy类型。
  4. WithTrackingDisposableTransients:
    • 跟踪并处理瞬态(Transient)生命周期的可释放对象。
  5. 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);
}

步骤详解

  1. ConfigureServices(services):
    • 配置Microsoft依赖注入服务集合。
  2. CreateContainerRules():
    • 创建并获取容器规则。
  3. new DryIoc.Container(CreateContainerRules()):
    • 使用自定义规则创建DryIoc容器实例。
  4. container.WithDependencyInjectionAdapter(services):
    • 将Microsoft依赖注入服务集合适配到DryIoc容器中。
  5. return new DryIocContainerExtension(container):
    • 返回DryIoc容器扩展实例。

通过上述配置,我们实现了Prism框架与DryIoc容器的集成。在DryIoc的新版本中,某些方法如WithoutFastExpressionCompiler()已经被移除,因此在使用这些版本时,需要确保代码适配最新的API变化。通过合理配置DryIoc的规则和容器扩展,可以充分发挥Prism框架的模块化和依赖注入功能,提高应用的可维护性和扩展性。

posted @ 2024-05-29 09:38  非法关键字  阅读(448)  评论(4编辑  收藏  举报