# Prism **Repository Path**: yzc95/prism ## Basic Information - **Project Name**: Prism - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-15 - **Last Updated**: 2026-09-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Prism for WPF / Avalonia Prism 是一套面向 WPF 与 Avalonia 的松耦合应用框架,帮助你写出可维护、可测试的 XAML 应用。它把一组常用设计模式落到可复用的库里,覆盖从启动、依赖注入、MVVM、命令、事件聚合,到模块化、区域组合、导航和对话框的完整桌面应用生命周期。 本仓库是 **Prism 8.1** 的实现:`Prism.Core` 只保留平台无关的 MVVM / 命令;区域导航、对话框、模块化合约与参数字典在 `Prism.Composition.Abstractions`(不依赖 Core);`Prism.Events` 为事件聚合叶子包(不依赖 Core);平台实现分别在 `Prism.Wpf` 与 `Prism.Avalonia`。WPF 启动入口通过 **DryIoc** 容器包 `Prism.DryIoc` 接入;Avalonia 使用 **`Prism.DryIoc.Avalonia`**(不能用 WPF 的 `Prism.DryIoc`,包 ID 会冲突)。本期不提供 `Prism.Avalonia.Application`。 > 英文主页见 [README.md](README.md)。官方文档在 [prismlibrary.com](http://prismlibrary.com/docs/),更完整的公开示例在 [Prism-Samples-Wpf](https://github.com/PrismLibrary/Prism-Samples-Wpf)。仓库内 [`e2e/Wpf`](e2e/Wpf) 与 [`e2e/Avalonia`](e2e/Avalonia) 是沙盒验证应用,用于本地与 CI 验证,不是最佳实践模板。 ## 目录 - [当前形态](#当前形态) - [仓库结构](#仓库结构) - [功能总览](#功能总览) - [架构与启动](#架构与启动) - [依赖注入](#1-依赖注入di) - [MVVM](#2-mvvm) - [源生成器](#3-源生成器) - [启用方式](#启用方式) - [MVVM 属性与命令](#mvvmprismcore) - [视图与对话框注册](#视图与对话框注册prismcomposition) - [命令](#4-命令command) - [事件聚合](#5-事件聚合event-aggregator) - [参数字典](#6-参数字典parameters) - [模块化](#7-模块化modularity) - [区域](#8-区域region) - [导航](#9-区域导航navigation) - [对话框](#10-对话框dialog) - [XAML 交互](#11-xaml-交互interactivity) - [生命周期接口](#12-生命周期接口) - [日志(Serilog / NLog)](#13-日志serilog) - [应用服务](#14-应用服务prismwpfapplication) - [示例用法](#示例用法) - [构建与测试](#构建与测试) - [上游与支持](#上游与支持) ## 当前形态 | 项 | 说明 | | --- | --- | | 版本 | 8.1(见 [`version.json`](version.json),由 Nerdbank.GitVersioning 生成修订号) | | 核心包 | `Prism.Core`(纯 MVVM)、`Prism.Composition.Abstractions`(区域 / 对话框 / 模块化合约)、`Prism.Events`(叶子包)、`Prism.Wpf`、`Prism.DryIoc`(程序集名 `Prism.DryIoc.dll`)、`Prism.Avalonia`、`Prism.DryIoc.Avalonia`;可选 `Prism.Logging.Serilog`、`Prism.Logging.NLog`、`Prism.Application.Abstractions`、`Prism.Wpf.Application` | | IoC 容器 | **仅 DryIoc 5.4.3**。仓库不含 Unity 实现或测试 | | 目标框架 | Core / Composition / Events / 容器:`netstandard2.0; net472; net8.0; net10.0`;WPF:`net472; net8.0-windows; net10.0-windows`;Avalonia:`net8.0; net10.0` | | SDK | [`global.json`](global.json) 要求 .NET SDK `10.0.100`(`rollForward: latestFeature`) | | 本仓库增强 | `Prism.Core.SourceGenerators`(`[BindableProperty]` / `[DelegateCommand]`)、`Prism.Composition.SourceGenerators`(`[NavigationView]` / `[Dialog]` / `[DialogWindow]`)、`Prism.Logging.Serilog` / `Prism.Logging.NLog`、.NET 10 目标、模块加载失败状态追踪等 | WPF 应用通常引用 **`Prism.DryIoc`**(传递 `Prism.Wpf`、Composition、Events、Core 与 DryIoc 适配层)。Avalonia 应用引用 **`Prism.DryIoc.Avalonia`**(传递 `Prism.Avalonia` 与 DryIoc 适配层)。**ViewModel / 无 UI 类库**引用 **`Prism.Core` + `Prism.Composition.Abstractions`**(需要事件再加 `Prism.Events`,需要消息框再加 `Prism.Application.Abstractions`),不要引用 `Prism.Wpf` / `Prism.Avalonia`。`Prism.Core` 与 `Prism.Events` 互不依赖;使用 IoC 抽象需显式引用 `Prism.Container.Abstractions`(或经平台包 / Composition 传递)。需要结构化日志时再按后端引用 **`Prism.Logging.Serilog`** 或 **`Prism.Logging.NLog`**。壳层 UX 仍在 **`Prism.Application.Abstractions` / `Prism.Wpf.Application`**(尚无 Avalonia Application 包)。MVVM 源生成器随 `Prism.Core` 分发,视图/对话框注册源生成器随 `Prism.Composition.Abstractions` 分发。 ## 仓库结构 ``` prism/ ├── src/ # 库源码 │ ├── Prism.Core/ # 纯 MVVM:BindableBase、命令 │ ├── Prism.Core.SourceGenerators/ # MVVM Roslyn 源生成器 │ ├── Prism.Events/ # EventAggregator(叶子包,不依赖 Core) │ ├── Composition/ │ │ ├── Prism.Composition.Abstractions/ # 区域 / 对话框 / 模块化合约、注册特性 │ │ └── Prism.Composition.SourceGenerators/ # 视图/对话框注册源生成器 │ ├── Containers/ │ │ ├── Prism.Container.Abstractions/ # DI 抽象 │ │ └── Prism.Container.DryIoc/ # DryIoc 适配 │ ├── Application/ │ │ └── Prism.Application.Abstractions/ # 跨平台壳层 UX 接口 │ ├── Logging/ │ │ ├── Prism.Logging.NLog/ # NLog + MEL 注册 │ │ └── Prism.Logging.Serilog/ # Serilog + MEL 注册 │ ├── Wpf/ │ │ ├── Prism.Wpf/ # 区域、导航、对话框、模块加载的 WPF 实现 │ │ ├── Prism.Wpf.Application/ # WPF 适配:调度、默认对话框、异常挂钩、文件框 │ │ └── Prism.DryIoc.Wpf/ # PrismApplication / PrismBootstrapper │ └── Avalonia/ │ ├── Prism.Avalonia/ # 区域、导航、对话框、模块加载的 Avalonia 实现 │ └── Prism.DryIoc.Avalonia/ # PrismApplication / PrismBootstrapper ├── tests/ # 单元测试 ├── e2e/Wpf/ # WPF HelloWorld 沙盒示例 ├── e2e/Avalonia/ # Avalonia HelloWorld 沙盒示例 ├── build/ # Azure Pipelines ├── images/ # README 配图 ├── PrismLibrary.slnx # 完整解决方案(库 + 测试) ├── PrismLibrary_Core.slnx # 非 UI 库 + 测试 ├── PrismLibrary_Wpf.slnx # WPF 子集(库 + 测试) ├── PrismLibrary_Avalonia.slnx # Avalonia 子集(库 + 测试) ├── Directory.Build.props # 全局 MSBuild 属性 ├── Directory.Packages.props # 中央包版本 └── version.json # 版本号 ``` ### 源码项目 | 项目路径 | NuGet 包 ID | 程序集 | 目标框架 | 职责 | | --- | --- | --- | --- | --- | | [`src/Prism.Core`](src/Prism.Core) | `Prism.Core` | `Prism.dll` | `netstandard2.0; net472; net8.0; net10.0` | `BindableBase`、命令、`ViewModelLocationProvider`;不引用 Events / Container / Composition | | [`src/Prism.Core.SourceGenerators`](src/Prism.Core.SourceGenerators) | (随 Core 打包为 Analyzer) | — | `netstandard2.0` | 为 `[BindableProperty]` / `[DelegateCommand]` 生成属性与命令 | | [`src/Prism.Events`](src/Prism.Events) | `Prism.Events` | 同名 | 同上 | `IEventAggregator`、`PubSubEvent`、`DelegateReference`;叶子包,不依赖 Core | | [`src/Composition/Prism.Composition.Abstractions`](src/Composition/Prism.Composition.Abstractions) | `Prism.Composition.Abstractions` | 同名 | 同上 | `IDialogService` / `IRegionManager` / `IModule`、`IParameters`、`[NavigationView]` / `[Dialog]` / `[DialogWindow]`;无 WPF、不依赖 Core | | [`src/Composition/Prism.Composition.SourceGenerators`](src/Composition/Prism.Composition.SourceGenerators) | (随 Composition 打包为 Analyzer) | — | `netstandard2.0` | 为 `[NavigationView]` / `[Dialog]` / `[DialogWindow]` 生成 `RegisterGenerated` 与名称常量 | | [`src/Containers/Prism.Container.Abstractions`](src/Containers/Prism.Container.Abstractions) | `Prism.Container.Abstractions` | 同名 | 同上 | `IContainerRegistry`、`IContainerProvider`、`ContainerLocator` | | [`src/Containers/Prism.Container.DryIoc`](src/Containers/Prism.Container.DryIoc) | `Prism.Container.DryIoc` | 同名 | 同上 | DryIoc 容器适配 | | [`src/Logging/Prism.Logging.NLog`](src/Logging/Prism.Logging.NLog) | `Prism.Logging.NLog` | 同名 | 同上 | 把 NLog 登记为 `ILogger` 后端 | | [`src/Logging/Prism.Logging.Serilog`](src/Logging/Prism.Logging.Serilog) | `Prism.Logging.Serilog` | 同名 | 同上 | 把 Serilog 登记为 `ILogger` 后端 | | [`src/Application/Prism.Application.Abstractions`](src/Application/Prism.Application.Abstractions) | `Prism.Application.Abstractions` | 同名 | 同上 | 壳层接口:`IDispatcher`、`IMessageService`、`IBusyService`、`IExceptionHandler`、`IFileDialogService` | | [`src/Wpf/Prism.Wpf`](src/Wpf/Prism.Wpf) | `Prism.Wpf` | `Prism.Wpf.dll` | `net472; net8.0-windows; net10.0-windows` | 区域、导航、对话框、模块加载、ViewModelLocator | | [`src/Wpf/Prism.Wpf.Application`](src/Wpf/Prism.Wpf.Application) | `Prism.Wpf.Application` | 同名 | `net472; net8.0-windows; net10.0-windows` | WPF 实现:调度、默认对话框、忙碌、异常挂钩、文件框 | | [`src/Wpf/Prism.DryIoc.Wpf`](src/Wpf/Prism.DryIoc.Wpf) | `Prism.DryIoc` | `Prism.DryIoc.dll` | 同上 | `PrismApplication`、`PrismBootstrapper` | | [`src/Avalonia/Prism.Avalonia`](src/Avalonia/Prism.Avalonia) | `Prism.Avalonia` | `Prism.Avalonia.dll` | `net8.0; net10.0` | 区域、导航、对话框、模块加载、ViewModelLocator(Avalonia 12) | | [`src/Avalonia/Prism.DryIoc.Avalonia`](src/Avalonia/Prism.DryIoc.Avalonia) | `Prism.DryIoc.Avalonia` | `Prism.DryIoc.Avalonia.dll` | `net8.0; net10.0` | `PrismApplication`、`PrismBootstrapper` | 条件编译约定:`*.net45.cs` 仅 .NET Framework;`*.netcore.cs` 仅 .NET Core / 5+;`*.Desktop.cs` 为桌面平台实现。 ### 测试与示例 | 路径 | 说明 | | --- | --- | | [`tests/Prism.Core.Tests`](tests/Prism.Core.Tests) | 命令、事件、MVVM、模块目录 | | [`tests/Prism.Core.SourceGenerators.Tests`](tests/Prism.Core.SourceGenerators.Tests) | MVVM 源生成器输出与诊断 | | [`tests/Prism.Composition.SourceGenerators.Tests`](tests/Prism.Composition.SourceGenerators.Tests) | 视图/对话框注册源生成器输出与诊断 | | [`tests/Containers/Prism.Container.DryIoc.Tests`](tests/Containers/Prism.Container.DryIoc.Tests) | DryIoc 注册 / 解析 | | [`tests/Logging/Prism.Logging.NLog.Tests`](tests/Logging/Prism.Logging.NLog.Tests) | `RegisterNLog` 解析、类别、写入与关闭行为 | | [`tests/Logging/Prism.Logging.Serilog.Tests`](tests/Logging/Prism.Logging.Serilog.Tests) | `RegisterSerilog` 解析 `ILogger` / SourceContext | | [`tests/Wpf/Prism.Wpf.Application.Tests`](tests/Wpf/Prism.Wpf.Application.Tests) | 应用服务登记、消息、忙碌、异常、调度、异步对话框、文件框 | | [`tests/Wpf/Prism.Wpf.Tests`](tests/Wpf/Prism.Wpf.Tests) | 区域、导航、模块、ViewModelLocator | | [`tests/Wpf/Prism.DryIoc.Wpf.Tests`](tests/Wpf/Prism.DryIoc.Wpf.Tests) | DryIoc + WPF 启动集成 | | [`tests/Avalonia/Prism.Avalonia.Tests`](tests/Avalonia/Prism.Avalonia.Tests) | Headless:区域适配器、ViewModelLocator、DialogService、模块目录 | | [`tests/Avalonia/Prism.DryIoc.Avalonia.Tests`](tests/Avalonia/Prism.DryIoc.Avalonia.Tests) | DryIoc + Avalonia 启动登记 | | [`e2e/Wpf/HelloWorld`](e2e/Wpf/HelloWorld) | `PrismApplication` 沙盒应用 | | [`e2e/Avalonia/HelloWorld`](e2e/Avalonia/HelloWorld) | Avalonia `PrismApplication` + 一个 Region + 一个对话框 + 一个 `IModule` | | [`e2e/Wpf/HelloWorld.Bootstraper`](e2e/Wpf/HelloWorld.Bootstraper) | `PrismBootstrapper` 对照启动 | | [`e2e/Wpf/HelloWorld.Core`](e2e/Wpf/HelloWorld.Core) | 对话框扩展方法 | | [`e2e/Wpf/Modules/HelloWorld.Modules.ModuleA`](e2e/Wpf/Modules/HelloWorld.Modules.ModuleA) | `IModule` + 导航视图 | ## 功能总览 Prism 把桌面应用拆成彼此解耦的能力层。业务代码面向接口编程,框架负责组装: | 能力 | 核心类型 | 解决的问题 | | --- | --- | --- | | 启动 | `PrismApplication` / `PrismBootstrapper` | 固定初始化顺序:容器 → 注册 → Shell → 模块 | | 依赖注入 | `IContainerRegistry` / `IContainerProvider` | 用构造函数注入替换 `new`,便于测试与替换实现 | | MVVM | `BindableBase`、`ViewModelLocator` | View 与 ViewModel 自动配对,属性变更通知 | | MVVM 源生成器 | `[BindableProperty]`、`[DelegateCommand]` | 少写样板属性与命令 | | 注册源生成器 | `[NavigationView]`、`[Dialog]`、`[DialogWindow]` | 少写 `RegisterForNavigation` / `RegisterDialog` | | 命令 | `DelegateCommand`、`AsyncDelegateCommand`、`CompositeCommand` | 把 UI 操作变成可测试的 `ICommand` | | 事件聚合 | `IEventAggregator`、`PubSubEvent` | 跨模块发布/订阅,无需互相引用 | | 模块化 | `IModule`、`IModuleCatalog`、`IModuleManager` | 按功能拆程序集,按需或启动时加载 | | 区域 | `IRegionManager`、Region Adapter / Behavior | 把窗口拆成可替换的 UI 插槽 | | 导航 | `RequestNavigate`、`INavigationAware` | 在区域内切换视图,带参数、确认、日志 | | 对话框 | `IDialogService`、`IDialogAware` | 用 ViewModel 弹出模态/非模态窗口 | | XAML 交互 | `InvokeCommandAction`、`ContainerProvider` | 把任意事件绑到命令;在 XAML 里解析服务 | | 日志 | `RegisterSerilog` / `RegisterNLog`、`ILogger` | 选择 Serilog 或 NLog 作为日志后端 | | 应用服务(可选) | `Prism.Application.Abstractions` 接口 + `Prism.Wpf.Application` 实现 | 壳层提示、忙碌、异常、配置、文件框;不进入 `Prism.Core`;尚无 `Prism.Avalonia.Application` | ```mermaid flowchart LR subgraph startup [启动] App[PrismApplication] end subgraph core [Core] DI[容器] MVVM[MVVM] Cmd[命令] Evt[事件聚合] Mod[模块] end subgraph wpf [WPF] Reg[区域] Nav[导航] Dlg[对话框] end App --> DI DI --> MVVM DI --> Cmd DI --> Evt DI --> Mod Mod --> Reg Reg --> Nav DI --> Dlg ``` ## 架构与启动 ### 分层与依赖 ```mermaid flowchart TD app[WPF 应用] DryIocWpf[Prism.DryIoc.Wpf] Wpf[Prism.Wpf] WpfApp[Prism.Wpf.Application] Comp[Prism.Composition.Abstractions] Core[Prism.Core] Events[Prism.Events] DryIoc[Prism.Container.DryIoc] Abs[Prism.Container.Abstractions] AppAbs[Prism.Application.Abstractions] SG[Prism.Core.SourceGenerators] CompSG[Prism.Composition.SourceGenerators] app --> DryIocWpf app --> WpfApp DryIocWpf --> Wpf DryIocWpf --> DryIoc WpfApp --> Wpf WpfApp --> AppAbs WpfApp --> Comp Wpf --> Core Wpf --> Comp Wpf --> Abs Wpf --> Events Comp --> Abs Comp --> CompSG Core --> SG DryIoc --> Abs ``` 业务代码面向 `Prism.Ioc`、`Prism.Mvvm`、`Prism.Regions`、`Prism.Services.Dialogs` 等抽象编程。ViewModel 项目引用 Composition 即可使用这些命名空间,无需 `Prism.Wpf`。`Prism.Events` 与 `Prism.Core` 互不依赖。只有需要改 DryIoc Rules 或调用底层 `IContainer` 时,才使用 `Prism.DryIoc` 的扩展方法。 ### 推荐入口:PrismApplication `Prism.DryIoc.PrismApplication` 继承 [`PrismApplicationBase`](src/Wpf/Prism.Wpf/PrismApplicationBase.cs)(本身继承 WPF `Application`)。XAML 根元素写成: ```xml ``` `OnStartup` 会调用 `InitializeInternal()`,顺序固定: ```mermaid flowchart TD onStartup[App.OnStartup] vmLocator[ConfigureViewModelLocator] container[CreateContainerExtension] catalog[CreateModuleCatalog] required[RegisterRequiredTypes] userTypes[RegisterTypes] finalize[FinalizeExtension] moduleCatalog[ConfigureModuleCatalog] adapters[ConfigureRegionAdapterMappings] behaviors[ConfigureDefaultRegionBehaviors] shell[CreateShell] autowire[AutowireViewModel 并绑定 RegionManager] initShell[InitializeShell 设为 MainWindow] modules[InitializeModules] show[OnInitialized 显示主窗口] onStartup --> vmLocator --> container --> catalog catalog --> required --> userTypes --> finalize finalize --> moduleCatalog --> adapters --> behaviors behaviors --> shell --> autowire --> initShell --> modules --> show ``` 可重写的钩子: | 钩子 | 必须重写 | 作用 | | --- | --- | --- | | `CreateContainerExtension()` | 容器包已实现 | DryIoc 版创建 `DryIocContainerExtension` | | `CreateModuleCatalog()` | 否,默认 `ModuleCatalog` | 换成目录扫描 / JSON / App.config | | `RegisterRequiredTypes()` | 一般不改 | 注册框架服务 | | `RegisterTypes()` | **是** | 注册应用服务、对话框、导航视图 | | `ConfigureModuleCatalog()` | 否 | `AddModule()` | | `ConfigureRegionAdapterMappings()` | 否 | 为自定义控件注册 Adapter | | `ConfigureDefaultRegionBehaviors()` | 否 | 增删默认 Region Behavior | | `CreateShell()` | **是** | 返回主窗口,通常 `Container.Resolve()` | | `InitializeShell()` | 否 | 默认 `MainWindow = shell` | | `InitializeModules()` | 否 | 默认 `ModuleManager.Run()` | | `OnInitialized()` | 否 | 默认 `MainWindow.Show()` | | `ConfigureViewModelLocator()` | 否 | 把默认工厂改成容器 `Resolve` | `RegisterRequiredTypes`(见 [`PrismInitializationExtensions.cs`](src/Wpf/Prism.Wpf/PrismInitializationExtensions.cs))注册: | 服务 | 默认实现 | 生命周期 | | --- | --- | --- | | `IModuleCatalog` | `CreateModuleCatalog()` 的实例 | Instance | | `IDialogService` | `DialogService` | Singleton | | `IModuleInitializer` | `ModuleInitializer` | Singleton | | `IModuleManager` | `ModuleManager` | Singleton | | `RegionAdapterMappings` | 自身 | Singleton | | `IRegionManager` | `RegionManager` | Singleton | | `IRegionNavigationContentLoader` | `RegionNavigationContentLoader` | Singleton | | `IEventAggregator` | `EventAggregator` | Singleton | | `IRegionViewRegistry` | `RegionViewRegistry` | Singleton | | `IRegionBehaviorFactory` | `RegionBehaviorFactory` | Singleton | | `IRegionNavigationJournalEntry` | `RegionNavigationJournalEntry` | Transient | | `IRegionNavigationJournal` | `RegionNavigationJournal` | Transient | | `IRegionNavigationService` | `RegionNavigationService` | Transient | | `IDialogWindow` | `DialogWindow` | Transient(默认对话框宿主) | ### Avalonia 启动:PrismApplication Avalonia 的 `Application.Initialize()` 由 `AppBuilder.Setup()` 调用,派生 `App` 必须先加载 AXAML,再跑 Prism: ```csharp public partial class App : PrismApplication { public override void Initialize() { AvaloniaXamlLoader.Load(this); base.Initialize(); } protected override AvaloniaObject CreateShell() => Container.Resolve(); protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterDialog(); } } ``` ```csharp [STAThread] public static void Main(string[] args) => AppBuilder.Configure() .UsePlatformDetect() .StartWithClassicDesktopLifetime(args); ``` `base.Initialize()` 的登记顺序与 WPF `InitializeInternal` 相同。`OnFrameworkInitializationCompleted` 把 Shell 赋给 `IClassicDesktopStyleApplicationLifetime.MainWindow`(或 `ISingleViewApplicationLifetime.MainView`)。**不要**再调用 `Window.Show()`,桌面 lifetime 会自己显示。`CreateShell()` 返回 `AvaloniaObject`。 区域适配器映射与 WPF 的差异:Avalonia 没有 WPF `Selector`,改为 `SelectingItemsControl` → `SelectorRegionAdapter`;`ItemsControl` / `ContentControl` 不变。默认 Region Behaviors 的键名与 WPF 相同,宿主类型是 `AvaloniaObject`。 xmlns 仍是 `http://prismlibrary.com/`。 ### 备选入口:PrismBootstrapper `Prism.DryIoc.PrismBootstrapper` 继承 `PrismBootstrapperBase`,初始化顺序与 `PrismApplication` 相同,但不继承 `Application`。适用于无法把 XAML 根改成 `prism:PrismApplication` 的项目。 ```csharp protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); new Bootstrapper().Run(); } ``` 差异:`CreateShell()` 返回 `DependencyObject` 而不是 `Window`;需要自己决定何时 `Show()`。 --- ## 1. 依赖注入(DI) Prism 不绑定某一家 IoC。抽象定义在 `Prism.Container.Abstractions`,经 `Prism.Core` 类型转发后以 `Prism.Ioc` 命名空间使用。 ### 三个核心接口 | 接口 | 职责 | | --- | --- | | `IContainerRegistry` | 注册:Instance / Singleton / Transient / Scoped / Many / 工厂 | | `IContainerProvider` | 解析:`Resolve`、按名称解析、`CreateScope()` | | `IContainerExtension` | 同时实现上面两者,并提供 `FinalizeExtension()`(注册结束后锁定/完成容器) | `PrismApplicationBase.Container` 的类型是 `IContainerProvider`。`RegisterTypes` 收到的是 `IContainerRegistry`,实际是同一个扩展对象。 ### 注册 API 泛型扩展在 [`IContainerRegistryExtensions`](src/Containers/Prism.Container.Abstractions/Ioc/IContainerRegistryExtensions.cs): ```csharp // 已有实例 containerRegistry.RegisterInstance(new SystemClock()); containerRegistry.RegisterInstance(clock, "test"); // 单例 containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(c => new MyService(c.Resolve())); containerRegistry.RegisterManySingleton(typeof(IFoo), typeof(IBar)); // 瞬态(每次 Resolve 新实例) containerRegistry.Register(); containerRegistry.Register(); containerRegistry.Register(c => new TransientImpl()); // 作用域(配合 CreateScope) containerRegistry.RegisterScoped(); // 查询 bool exists = containerRegistry.IsRegistered(); bool named = containerRegistry.IsRegistered("test"); ``` WPF 专用扩展([`src/Wpf/Prism.Wpf/Ioc/IContainerRegistryExtensions.cs`](src/Wpf/Prism.Wpf/Ioc/IContainerRegistryExtensions.cs)): ```csharp containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation("CustomName"); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialogWindow(); containerRegistry.RegisterDialogWindow("AnotherDialogWindow"); ``` `RegisterForNavigation` 把视图按名称注册为 `object`;若同时指定 ViewModel,还会调用 `ViewModelLocationProvider.Register`。名称默认是类型名(`typeof(T).Name`)。 也可在 View 上标 `[NavigationView]` / `[Dialog]` / `[DialogWindow]`,再调用 `containerRegistry.RegisterGenerated()`,由源生成器写出上述注册调用,详见 [源生成器](#3-源生成器)。 ### 解析与作用域 ```csharp var service = container.Resolve(); var named = container.Resolve("test"); using IScopedProvider scope = container.CreateScope(); var uow = scope.Resolve(); ``` `IScopedProvider` 表示一次作用域。解析失败抛出 `ContainerResolutionException`(可携带内部错误集合)。 ### 全局入口 - `ContainerLocator.Current` / `ContainerLocator.Container`:进程内静态容器。`ViewModelLocator` 默认工厂和部分 Region 内部解析走这里。 - 启动时由 `ContainerLocator.SetContainerExtension(...)` 设置;测试里可重置。 ### XAML 中解析 [`ContainerProviderExtension`](src/Wpf/Prism.Wpf/Ioc/ContainerProviderExtension.cs) 可在 XAML 里从容器取对象: ```xml ``` 可用 `Name` 解析命名注册。 ### DryIoc 细节 - `DryIocContainerExtension` 包装 DryIoc `Container`,默认 Rules 含具体类型动态注册等。 - `PrismApplication.CreateContainerRules()` 可重写以自定义 Rules。 - `containerRegistry.GetContainer()`(`Prism.Container.DryIoc` 的扩展)取出底层 `IContainer`,仅在需要 DryIoc 专有 API 时使用。 --- ## 2. MVVM ### BindableBase [`BindableBase`](src/Prism.Core/Mvvm/BindableBase.cs) 实现 `INotifyPropertyChanged`: ```csharp public class PersonViewModel : BindableBase { private string _name = string.Empty; public string Name { get => _name; set => SetProperty(ref _name, value); } } ``` - `SetProperty(ref storage, value)`:值变化才赋值并 `RaisePropertyChanged`,返回是否变化。 - `SetProperty(ref storage, value, onChanged)`:变化后额外执行回调。 - `RaisePropertyChanged()` / `OnPropertyChanged(args)`:手动通知。 ### ViewModelLocator XAML: ```xml ``` 查找顺序([`ViewModelLocationProvider`](src/Prism.Core/Mvvm/ViewModelLocationProvider.cs)): 1. 为该 View 类型注册的工厂 `Register(Func)` 2. 为该 View 类型注册的 ViewModel 类型 `Register()` 3. **约定**:把完整名中的 `.Views.` 换成 `.ViewModels.`;若类名以 `View` 结尾则加 `Model`,否则加 `ViewModel`。 例:`HelloWorld.Views.MainWindow` → `HelloWorld.ViewModels.MainWindowViewModel` 例:`ModuleA.Views.ViewA` → `ModuleA.ViewModels.ViewAViewModel` Prism 启动时把默认工厂改成 `ContainerLocator.Container.Resolve(type)`,因此 ViewModel 的构造函数依赖会被注入。设计时(`DesignerProperties.IsInDesignMode`)不会自动接线。 自定义约定: ```csharp ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(viewType => ...); ViewModelLocationProvider.SetDefaultViewModelFactory((view, vmType) => container.Resolve(vmType)); ``` ### ErrorsContainer [`ErrorsContainer`](src/Prism.Core/Mvvm/ErrorsContainer.cs) 帮助实现 `INotifyDataErrorInfo`: ```csharp private readonly ErrorsContainer _errors; public PersonViewModel() { _errors = new ErrorsContainer(pn => RaiseErrorsChanged(pn)); } public bool HasErrors => _errors.HasErrors; public IEnumerable GetErrors(string propertyName) => _errors.GetErrors(propertyName); _errors.SetErrors(() => Name, new[] { "名称不能为空" }); _errors.ClearErrors(() => Name); _errors.ClearErrors(); ``` --- ## 3. 源生成器 两套 **Roslyn 增量源生成器**,在编译期写 `.g.cs`,不进入运行时。无相关特性时不生成文件。IDE 里可在依赖项 / Analyzers 下展开生成文件核对输出。 | 生成器 | 随哪个 NuGet 分发 | 特性 | 产出 | | --- | --- | --- | --- | | [`Prism.Core.SourceGenerators`](src/Prism.Core.SourceGenerators) | `Prism.Core` | `[BindableProperty]`、`[NotifyPropertyChangedFor]`、`[NotifyCanExecuteChangedFor]`、`[DelegateCommand]` | 可绑定属性与命令属性 | | [`Prism.Composition.SourceGenerators`](src/Composition/Prism.Composition.SourceGenerators) | `Prism.Composition.Abstractions` | `[NavigationView]`、`[Dialog]`、`[DialogWindow]` | `RegisterGenerated()` 与名称常量 | 特性定义与 Analyzer 分开:MVVM 特性在 `Prism.Core`(`Prism.Mvvm` / `Prism.Commands`),注册特性在 `Prism.Composition.Abstractions`(`Prism.Ioc`)。生成器只认这些 FQN,与 UI 框架无关。带 ViewModel 的 `RegisterForNavigation`、`RegisterDialogWindow` 仍由 **WPF / Avalonia 平台包**提供同名扩展;生成器只写出调用文本。 ### 启用方式 - **NuGet**:引用 `Prism.Core` / `Prism.Composition.Abstractions`(WPF 应用经 `Prism.DryIoc` → `Prism.Wpf` 会传递 Composition)即可带上 Analyzer。 - **本仓库源码工程**:`ProjectReference` **不会**把 Analyzer 传到下一跳。消费项目需再挂生成器,例如 HelloWorld / ModuleA: ```xml ``` 改特性或生成器后,对消费项目做一次 Rebuild。生成代码标有 `[GeneratedCode]`,并 `#pragma warning disable`。 ### MVVM(Prism.Core) 给 `BindableBase` 子类少写 `SetProperty` 与 `DelegateCommand` 样板。每个符合条件的类型生成一份 `{命名空间}.{类型}.g.cs`。 **目标类型必须同时满足:** - `partial class`(嵌套类型时,外层类型也必须 `partial`) - 继承 `Prism.Mvvm.BindableBase`(含间接继承) - 不是 `file` 局部类型 | 特性 | 命名空间 | 施加目标 | 生成结果 | | --- | --- | --- | --- | | `[BindableProperty]` | `Prism.Mvvm` | 实例字段,或未实现的 partial 属性 | 带相等性检查与 `SetProperty` 的公共属性 | | `[NotifyPropertyChangedFor("X", ...)]` | `Prism.Mvvm` | 同上,可重复 | setter 里再 `RaisePropertyChanged("X")` | | `[NotifyCanExecuteChangedFor("SaveCommand", ...)]` | `Prism.Mvvm` | 同上,可重复 | setter 里再 `SaveCommand.RaiseCanExecuteChanged()` | | `[DelegateCommand]` | `Prism.Commands` | 实例方法 | 懒创建的 `DelegateCommand` / `AsyncDelegateCommand` 属性 | #### `[BindableProperty]` 标在**字段**上时,去掉前缀 `_` 或 `m_` 再首字母大写得到属性名:`_firstName` / `m_firstName` → `FirstName`。不能是 `static` / `const` / `readonly` / `ref` / fixed。字段上的 `[property: ...]` 特性会抄到生成属性上(便于 XAML / 校验特性)。 标在**属性**上时,该属性必须是尚未写 get/set 体的 `partial` 实例属性(同时有 getter 与 setter)。生成器会再写一个后备字段。 setter 顺序:相等则返回 → `OnXxxChanging(new)` / `OnXxxChanging(old, new)` → `SetProperty` → `OnXxxChanged` → 额外属性通知 → 命令 `CanExecute` 刷新。可在同类中实现这些 `partial` 方法做校验或副作用: ```csharp partial void OnFirstNameChanging(string value); partial void OnFirstNameChanging(string oldValue, string newValue); partial void OnFirstNameChanged(string value); ``` #### `[DelegateCommand]` | 参数 | 含义 | | --- | --- | | `CanExecute` | CanExecute **方法名**或无参 `bool` **属性名** | | `AllowConcurrentExecutions` | 仅异步有效;生成 `.EnableParallelExecution()` | | `CommandName` | 生成的属性名;默认「方法名去掉末尾 `Async` + `Command`」 | 支持的 Execute 签名: - 返回 `void` → `DelegateCommand` / `DelegateCommand` - 返回 `Task` / `Task` / `ValueTask` / `ValueTask` → `AsyncDelegateCommand`(`ValueTask` 会包一层 `.AsTask()`) - 0 个业务参数,或 1 个 payload;异步方法还可在末尾加 `CancellationToken` - payload **不能是非可空值类型**(XAML 会传入 `null`),用 `int?` 等 `CanExecute` 若指向方法:无参命令则方法无参;有 payload 则方法参数类型必须与 Execute 一致,返回 `bool`。不能标在 `static` / 泛型方法 / `partial` 定义上。 ```csharp public partial class EditorViewModel : BindableBase { [BindableProperty] [NotifyPropertyChangedFor(nameof(FullName))] [NotifyCanExecuteChangedFor(nameof(SaveCommand))] private string _firstName = string.Empty; public string FullName => _firstName; [DelegateCommand(CanExecute = nameof(CanSave))] private void Save() { } private bool CanSave() => !string.IsNullOrEmpty(_firstName); [DelegateCommand] private async Task LoadAsync() => await Task.Delay(100); [DelegateCommand(CommandName = "OpenItemCommand")] private void Open(string? path) { } } ``` 上例生成 `FirstName`、`SaveCommand`(`DelegateCommand`)、`LoadCommand`(`AsyncDelegateCommand`)、`OpenItemCommand`(`DelegateCommand`)。命令属性懒初始化,可直接绑 `Button.Command`。 | Id | 何时出现 | | --- | --- | | `PRISM0001` | 类型不是 `partial` | | `PRISM0002` | 未继承 `BindableBase` | | `PRISM0003` | 外层类型不是 `partial` | | `PRISM0004` | 同步方法上用了 `AllowConcurrentExecutions` | | `PRISM0005` | `CanExecute` 成员不存在 | | `PRISM0006` | `CanExecute` 签名与 Execute 不匹配 | | `PRISM0007` | Execute 方法签名不支持 | | `PRISM0008` | payload 是非可空值类型 | | `PRISM0009` | 生成成员名与已有成员冲突 | | `PRISM0010` | 通知目标不是合法标识符 | | `PRISM0011` | `[BindableProperty]` 标在不支持的字段/属性上 | | `PRISM0012` | `file` 局部类型 | ### 视图与对话框注册(Prism.Composition) 给 **View / 对话框窗口** 标特性,编译期写出本程序集的容器注册。运行时仍走现有 `RegisterForNavigation` / `RegisterDialog` / `RegisterDialogWindow`,行为与手写相同。 每个消费程序集最多一份 `PrismGenerated.g.cs`(无特性则不生成),内容是: - `NavigationNames` / `DialogNames` / `DialogWindowNames` 字符串常量 - `IContainerRegistry.RegisterGenerated()` 扩展(返回 `containerRegistry`,可链式调用) 生成类型的命名空间是项目的 `RootNamespace`;未设置则用程序集名。在 `App.RegisterTypes` 或 `IModule.RegisterTypes` 里**必须手动调一行**,生成器不会改启动流程: ```csharp containerRegistry.RegisterGenerated(); ``` | 特性 | 标在 | 生成调用 | | --- | --- | --- | | `[NavigationView]` | 导航 View | `RegisterForNavigation(name)` | | `[NavigationView(ViewModel = typeof(TVm))]` | 同上 | `RegisterForNavigation(name)`(WPF 侧会 `ViewModelLocationProvider.Register`) | | `[Dialog]` / `[Dialog(Name = "...")]` | 对话框内容 View | `RegisterDialog(name)` | | `[Dialog(typeof(TVm))]` | 同上 | `RegisterDialog(name)`,`TVm` 必须实现 `IDialogAware` | | `[DialogWindow]` | 对话框宿主窗口 | `RegisterDialogWindow()`,作为**默认**宿主,**不**进 `DialogWindowNames` | | `[DialogWindow("Key")]` | 同上 | `RegisterDialogWindow(name)`,窗口必须实现 `IDialogWindow` | `Name` 省略时用类型短名,与手写 `RegisterForNavigation()` 一致。`Name` 必须是合法 C# 标识符才能生成常量;否则报 `PRISMCOMP0004`,注册仍用字符串字面量。 ```csharp [NavigationView] public partial class ViewA : UserControl { } [Dialog(typeof(NotificationDialogViewModel))] public partial class NotificationDialog : UserControl { } [DialogWindow] public partial class CustomDialogWindow : Window, IDialogWindow { } [DialogWindow(nameof(AnotherDialogWindow))] public partial class AnotherDialogWindow : Window, IDialogWindow { } ``` 大致生成(命名空间随项目而定): ```csharp public static class NavigationNames { public const string ViewA = "ViewA"; } public static class DialogNames { public const string NotificationDialog = "NotificationDialog"; } public static class DialogWindowNames { public const string AnotherDialogWindow = "AnotherDialogWindow"; } public static class PrismGenerated { public static IContainerRegistry RegisterGenerated(this IContainerRegistry containerRegistry) { containerRegistry.RegisterForNavigation(NavigationNames.ViewA); containerRegistry.RegisterDialog(DialogNames.NotificationDialog); containerRegistry.RegisterDialogWindow(); containerRegistry.RegisterDialogWindow(DialogWindowNames.AnotherDialogWindow); return containerRegistry; } } ``` 导航、弹窗时用常量,避免魔法字符串: ```csharp _regionManager.RequestNavigate("ContentRegion", NavigationNames.ViewA); _dialogService.ShowDialog(DialogNames.NotificationDialog, null, null, DialogWindowNames.AnotherDialogWindow); ``` **同一程序集**内,导航名与对话框名共用一套「视图名」空间,窗口名单独一套:两个 `[NavigationView]` / `[Dialog]` 不能同名;两个命名 `[DialogWindow]` 不能同名。冲突的那条会跳过生成并报 `PRISMCOMP0001`。同一类型不能同时标多种注册特性(`PRISMCOMP0005`),该类型整条跳过。 **多程序集**:每个程序集各有一份 `RegisterGenerated`。模块的 `RegisterTypes` 调自己的那份;壳层再调壳层的。HelloWorld 与 ModuleA 若同时 `using` 两边的根命名空间,扩展方法会歧义——模块里只 using 自己的命名空间。HelloWorld.Core 不能引用宿主生成的 `DialogNames`(循环引用),因此对话框扩展仍用与类型短名一致的字符串。 | Id | 含义 | 是否仍生成该条注册 | | --- | --- | --- | | `PRISMCOMP0001` | 同一程序集注册名冲突 | 冲突项跳过 | | `PRISMCOMP0002` | `[Dialog]` 的 ViewModel 未实现 `IDialogAware` | 跳过 | | `PRISMCOMP0003` | `[DialogWindow]` 未实现 `IDialogWindow` | 跳过 | | `PRISMCOMP0004` | `Name` 不是合法标识符 | 仍注册,常量改用字面量 | | `PRISMCOMP0005` | 同一类型标了多种注册特性 | 该类型跳过 | 本期不做:`[Module]` / `AddGeneratedModules`、`[RegionView]` / `RegisterViewWithRegion`、隐式 `ModuleInitializer`。 --- ## 4. 命令(Command) 命名空间 `Prism.Commands`。全部实现 `ICommand`,可直接绑到 `Button.Command`。 ### DelegateCommand / DelegateCommand\ ```csharp SaveCommand = new DelegateCommand(Save, CanSave) .ObservesProperty(() => Name); OpenCommand = new DelegateCommand(Navigate) .ObservesCanExecute(() => IsReady); ``` - 无参:`DelegateCommand(Action)` / `(Action, Func)` - 有参:`DelegateCommand(Action)`。**T 不能是非可空值类型**(因为 XAML 初始化会传入 `null`)。请用 `int?` 等可空类型。 - `ObservesProperty(() => X)`:`X` 变更时自动 `RaiseCanExecuteChanged` - `ObservesCanExecute(() => IsReady)`:用该 bool 属性作为 CanExecute,并观察其变更 - 基类 `DelegateCommandBase.RaiseCanExecuteChanged()` 可手动刷新 ### AsyncDelegateCommand / AsyncDelegateCommand\ 实现 `IAsyncCommand`。默认同一时间只执行一次:执行中 `IsExecuting == true` 且 `CanExecute` 为 false。 ```csharp LoadCommand = new AsyncDelegateCommand(LoadAsync, CanLoad) .CancelAfter(TimeSpan.FromSeconds(30)) .Catch(ex => Status = ex.Message) .ObservesProperty(() => Query); LoadCommand = new AsyncDelegateCommand(LoadAsync) .EnableParallelExecution(); ``` | API | 作用 | | --- | --- | | `Execute(CancellationToken?)` | 异步执行 | | `IsExecuting` | 是否正在执行(会通知属性变更) | | `EnableParallelExecution()` | 允许重叠执行 | | `CancelAfter(TimeSpan)` | 默认超时取消 | | `CancellationTokenSourceFactory(...)` | 自定义默认 Token | | `Catch(Action)` | 捕获执行/CanExecute 异常,避免冒泡 | 源生成器对 `async Task` 方法会生成 `AsyncDelegateCommand`。 ### CompositeCommand 把多个 `ICommand` 合成一个:`Execute` 转发给全部已注册命令;`CanExecute` 仅当**至少有一个应执行的命令且它们都能执行**时为 true。 ```csharp var saveAll = new CompositeCommand(monitorCommandActivity: true); saveAll.RegisterCommand(moduleA.SaveCommand); saveAll.RegisterCommand(moduleB.SaveCommand); saveAll.UnregisterCommand(moduleB.SaveCommand); ``` `monitorCommandActivity: true` 时,只执行实现了 `IActiveAware` 且 `IsActive == true` 的子命令。适合「当前活动文档的保存」这类场景。 ### 在 ViewModel 中的典型写法(手写) ```csharp public ICommand SaveCommand { get; } public DocumentViewModel() { SaveCommand = new DelegateCommand(Save, CanSave) .ObservesProperty(() => Title); } ``` --- ## 5. 事件聚合(Event Aggregator) 模块之间不要互相引用具体类型时,用 [`IEventAggregator`](src/Prism.Events/Events/IEventAggregator.cs) 做发布/订阅(包 `Prism.Events`,不依赖 Core)。框架以 **Singleton** 注册。 ### 定义事件 ```csharp public class StatusMessageEvent : PubSubEvent { } public class AppClosingEvent : PubSubEvent { } // 无载荷 ``` 同类事件在聚合器内是单例:多次 `GetEvent()` 得到同一实例。 ### 发布与订阅 ```csharp public class PublisherViewModel { private readonly IEventAggregator _ea; public PublisherViewModel(IEventAggregator ea) => _ea = ea; public void Notify() => _ea.GetEvent().Publish("就绪"); } public class SubscriberViewModel { public SubscriberViewModel(IEventAggregator ea) { ea.GetEvent().Subscribe( msg => Status = msg, ThreadOption.UIThread, keepSubscriberReferenceAlive: false, filter: msg => msg.StartsWith("就绪")); } } ``` `Subscribe` 重载可组合: | 参数 | 默认 | 含义 | | --- | --- | --- | | `threadOption` | `PublisherThread` | 回调线程 | | `keepSubscriberReferenceAlive` | `false` | `false` 用弱引用,订阅者可被 GC;`true` 强引用,**必须** `Unsubscribe` | | `filter` | 全放行 | 仅 `PubSubEvent`:载荷谓词 | `ThreadOption`: | 值 | 行为 | | --- | --- | | `PublisherThread` | 在发布线程同步调用 | | `UIThread` | 已在 UI 同步上下文则内联,否则异步 Post | | `BackgroundThread` | 后台线程异步执行 | 退订: ```csharp var token = ea.GetEvent().Subscribe(OnMsg); ea.GetEvent().Unsubscribe(OnMsg); token.Dispose(); // SubscriptionToken 也可退订 bool still = ea.GetEvent().Contains(OnMsg); ``` `EventAggregator` 在构造时捕获 `SynchronizationContext.Current`,因此请在 UI 线程创建(Prism 启动时已满足)。集合本身线程安全。弱引用订阅在发布时会清掉已死亡的订阅。 --- ## 6. 参数字典(Parameters) 导航和对话框共用 [`IParameters`](src/Composition/Prism.Composition.Abstractions/Common/IParameters.cs) / `ParametersBase`(包 `Prism.Composition.Abstractions`): - `NavigationParameters`:区域导航 - `DialogParameters`:对话框 ```csharp var p = new DialogParameters { { "message", "Hello" }, { "count", 3 } }; // 或查询字符串风格 var p2 = new DialogParameters("message=Hello&count=3"); string msg = p.GetValue("message"); if (p.TryGetValue("count", out var n)) { } bool has = p.ContainsKey("message"); ``` URI 导航也可带查询:`RequestNavigate("ContentRegion", "ViewA?id=1")`。 --- ## 7. 模块化(Modularity) 模块把功能切到独立程序集,主程序只依赖模块契约(或通过目录发现 DLL)。 ### IModule ```csharp public interface IModule { void RegisterTypes(IContainerRegistry containerRegistry); void OnInitialized(IContainerProvider containerProvider); } ``` 1. **`RegisterTypes`**:注册本模块的服务、导航视图、对话框。此时 Shell 已创建、区域已更新。 2. **`OnInitialized`**:解析服务、向区域 `RequestNavigate` / `RegisterViewWithRegion`、订阅事件等。 ```csharp public class ModuleAModule : IModule { public void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterForNavigation(); containerRegistry.RegisterSingleton(); } public void OnInitialized(IContainerProvider containerProvider) { var rm = containerProvider.Resolve(); rm.RequestNavigate("ContentRegion", "ViewA"); } } ``` ### 声明依赖与发现特性 ```csharp [Module(ModuleName = "ModuleA", OnDemand = false)] [ModuleDependency("InfrastructureModule")] public class ModuleAModule : IModule { } ``` - `[Module]`(WPF):自定义模块名、是否按需加载(`OnDemand` 对应 `InitializationMode.OnDemand`) - `[ModuleDependency("OtherModule")]`:可多次施加;目录初始化时做拓扑排序,循环依赖抛 `CyclicDependencyFoundException` ### 模块目录 | 类型 | 平台 | 用法 | | --- | --- | --- | | `ModuleCatalog` | 全部 | 代码 `AddModule()` | | `AssemblyScanningModuleCatalog` | WPF / Avalonia | 抽象基类:子类只负责「发现」DLL,基类负责元数据检查与生成 `ModuleInfo` | | `DirectoryModuleCatalog` | WPF / Avalonia | `ModulePath = "Modules"`,扫描单目录 DLL 上的 `[Module]`(继承自 `AssemblyScanningModuleCatalog`) | | `JsonModuleCatalog` | .NET Core+ | `new JsonModuleCatalog("modules.json")` | | `ConfigurationModuleCatalog` | .NET Framework | 读 `App.config` 的 `` | 代码注册: ```csharp protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { moduleCatalog.AddModule(); moduleCatalog.AddModule( InitializationMode.WhenAvailable, dependsOn: nameof(InfrastructureModule)); moduleCatalog.AddModule(InitializationMode.OnDemand); } ``` `AddModule` 还有按名称、程序集限定名、`Ref`(外部文件路径)的重载。 HelloWorld 默认: ```csharp protected override IModuleCatalog CreateModuleCatalog() => new DirectoryModuleCatalog { ModulePath = "Modules" }; ``` #### 自定义程序集发现 `DirectoryModuleCatalog` 只扫一个扁平目录。若各模块在独立文件夹中,可继承 `AssemblyScanningModuleCatalog`,只重写 `DiscoverAssemblyFiles()`: ```csharp public sealed class MultiDirectoryModuleCatalog : AssemblyScanningModuleCatalog { public required IReadOnlyList ModulePaths { get; init; } protected override IEnumerable DiscoverAssemblyFiles() => ModulePaths.SelectMany(path => new DirectoryInfo(path).GetFiles("*.dll")); } // CreateModuleCatalog: return new MultiDirectoryModuleCatalog { ModulePaths = ["Modules/ModuleA", "Modules/ModuleB"] }; ``` 检查阶段(`MetadataLoadContext` / ReflectionOnly)会跳过非托管或无效 DLL,并尝试从发现到的程序集路径解析依赖。 ### 加载时机与状态 `InitializationMode`: - `WhenAvailable`:`ModuleManager.Run()` 时立即加载 - `OnDemand`:之后调用 `moduleManager.LoadModule("ReportsModule")` 或 `LoadModule()` `ModuleState`:`NotStarted` → `LoadingTypes` → `ReadyForInitialization` → `Initializing` → `Initialized`(失败为 `Failed`)。 `IModuleManager`: ```csharp manager.Run(); manager.LoadModule(); bool exists = manager.ModuleExists(); bool ready = manager.IsModuleInitialized("ModuleAModule"); ModuleState state = manager.GetModuleState(); manager.LoadModuleCompleted += (_, e) => { /* e.ModuleInfo, e.Error, e.IsCanceled */ }; manager.ModuleDownloadProgressChanged += (_, e) => { /* 远程/文件加载进度 */ }; ``` 加载流程:初始化目录并验证依赖 → 加载 `WhenAvailable` 模块(`FileModuleTypeLoader` 可从磁盘拉程序集)→ `ModuleInitializer` 用容器创建模块实例 → `RegisterTypes` → `OnInitialized`。重复名抛 `DuplicateModuleException`;类型找不到抛 `ModuleNotFoundException` / `ModuleTypeLoadingException`。 --- ## 8. 区域(Region) 区域是窗口里的 **UI 插槽**:主窗口定义「哪里可以放内容」,模块往里面塞视图,彼此不引用具体控件树。 ### 声明区域 ```xml ``` 控件加入可视化树后,[`DelayedRegionCreationBehavior`](src/Wpf/Prism.Wpf/Regions/Behaviors/DelayedRegionCreationBehavior.cs) 按控件类型找 Adapter,创建 `IRegion` 并登记到 `IRegionManager`。 附加属性: | 属性 | 作用 | | --- | --- | | `RegionManager.RegionName` | 区域名 | | `RegionManager.RegionManager` | 指定使用的 RegionManager(作用域) | | `RegionManager.RegionContext` | 向区域内视图传递上下文 | ### Adapter(控件 → 区域类型) | 控件 | Adapter | 区域类型 | 含义 | | --- | --- | --- | --- | | `ContentControl` | `ContentControlRegionAdapter` | `SingleActiveRegion` | 同时只有一个活动视图(内容被替换) | | `Selector`(`TabControl`、`ListBox` 等) | `SelectorRegionAdapter` | `SingleActiveRegion` | 选中项即活动视图 | | `ItemsControl` | `ItemsControlRegionAdapter` | `AllActiveRegion` | 所有视图始终活动,不能 Deactivate | `SingleActiveRegion`:激活新视图时自动停用旧视图。 `AllActiveRegion`:调用 `Deactivate` 会抛异常。 `Region` 基类允许多个活动视图。 自定义控件可在 `ConfigureRegionAdapterMappings` 里 `RegisterMapping()`。 ### 向区域添加视图(不经过导航) ```csharp regionManager.RegisterViewWithRegion("ToolbarRegion", typeof(ToolbarView)); regionManager.RegisterViewWithRegion("ToolbarRegion", "ToolbarView"); regionManager.RegisterViewWithRegion("ToolbarRegion", () => container.Resolve()); regionManager.AddToRegion("ContentRegion", viewInstance); ``` `RegisterViewWithRegion` 由 `AutoPopulateRegionBehavior` 在区域创建时解析并加入。适合工具栏、状态栏这种「区域一出现就显示」的视图。 ### 区域 API ```csharp IRegion region = regionManager.Regions["ContentRegion"]; region.Add(view); region.Add(view, "viewName", createRegionManagerScope: true); region.AddRange(views); region.Remove(view); region.RemoveRange(views); region.Activate(view); region.Deactivate(view); region.GetView("viewName"); foreach (var v in region.Views) { } foreach (var v in region.ActiveViews) { } region.Context = someSharedState; region.SortComparison = ...; ``` `createRegionManagerScope: true` 会为该视图创建子 `IRegionManager`(`CreateRegionManager()`),用于子窗口/多项文档里的嵌套区域。 ### Region Behavior(默认 7 个) | Behavior | 作用 | | --- | --- | | `BindRegionContextToDependencyObjectBehavior` | 把 `IRegion.Context` 同步到视图 | | `RegionActiveAwareBehavior` | 激活变化时设置 View/ViewModel 的 `IActiveAware.IsActive` | | `SyncRegionContextWithHostBehavior` | 宿主上的 `RegionContext` 与区域同步 | | `RegionMemberLifetimeBehavior` | 停用时按 `IRegionMemberLifetime` / `[RegionMemberLifetime]` 决定是否移除 | | `ClearChildViewsRegionBehavior` | 区域从 RegionManager 移除时清理子视图 | | `AutoPopulateRegionBehavior` | 自动加入 `RegisterViewWithRegion` 的视图 | | `DestructibleRegionBehavior` | 视图移除时对实现 `IDestructible` 的对象调用 `Destroy()` | 可在 `ConfigureDefaultRegionBehaviors` 中 `AddIfMissing` 或去掉某项。 Region 的宿主注册由继承型 `RegionManager` 附加属性在 Region 创建时立即完成,不必等控件 `Loaded` / 挂到可视树;更换或清空 manager 时会迁移或注销。不再依赖全局 `UpdateRegions` 轮询。 ### 8.1 迁移(Regions breaking) 相对此前的轮询模型,下列 API 已删除,升级时需改调用方: | 删除项 | 替代 | | --- | --- | | `RegionManager.UpdateRegions()` / `UpdatingRegions` | 无需手动刷新。设好 `RegionName` 与 `RegionManager` 后即可 `Regions[name]` / `RequestNavigate` | | `UpdateRegionsException` | 创建失败改为 `RegionCreationException` | | `RegionManagerRegistrationBehavior`、`IRegionManagerAccessor` | 由继承型 `RegionManager` 附加属性完成注册 | | `ItemMetadata`、`ViewsCollection` | 不要再依赖这些实现类型;消费 `IViewsCollection` 即可 | 实现 `IRegion` 时需补 `AddRange` / `RemoveRange`。`IViewsCollection` 现为 `IReadOnlyList`(需 `Count` 与索引器)。`AddRange` / `RemoveRange` 对多项发一次 `CollectionChanged.Reset`,自定义 Behavior 若只处理 `Add`/`Remove` 会漏事件。`AddRange` 不支持 `viewName` 与 `createRegionManagerScope`。宿主暂时离开可视树时 Region **不会**从 manager 注销。 ### 视图排序与生命周期特性 ```csharp [ViewSortHint("010")] public class FirstView : UserControl { } [RegionMemberLifetime(KeepAlive = false)] public class TransientView : UserControl { } [SyncActiveState] public class ChildView : UserControl { } ``` - `[ViewSortHint]`:字符串比较,决定 `ItemsControl`/`TabControl` 中顺序 - `[RegionMemberLifetime(KeepAlive = false)]` 或实现 `IRegionMemberLifetime`:停用即从区域删除(配合导航可销毁页面) - `[SyncActiveState]`:子视图随父视图激活状态同步 ### RegionContext 宿主: ```xml ``` 视图侧可通过 `RegionContext.GetObservableContext(view)` 观察上下文变化。 --- ## 9. 区域导航(Navigation) 导航 = 按名称在区域内切换已注册视图,并走一套生命周期。 ### 注册与请求 ```csharp containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation("A"); regionManager.RequestNavigate("ContentRegion", "ViewA"); regionManager.RequestNavigate("ContentRegion", "ViewA", result => { if (!(result.Result ?? false)) { /* 被取消或失败 */ } }); regionManager.RequestNavigate("ContentRegion", "ViewA", new NavigationParameters { { "id", 42 } }); regionManager.RequestNavigate("ContentRegion", new Uri("ViewA?id=42", UriKind.Relative)); ``` 也可在视图上标 `[NavigationView]`,再 `RegisterGenerated()`,并用 `NavigationNames` 代替字符串,详见 [源生成器](#3-源生成器)。 内部:`IRegionNavigationContentLoader` 用名称从容器解析 `object`,加入区域并 `Activate`。 ### 导航生命周期 对**当前活动视图及其 DataContext**,以及**目标视图及其 DataContext**调用: ```mermaid sequenceDiagram participant Nav as RegionNavigationService participant From as 当前视图或VM participant To as 目标视图或VM Nav->>From: IConfirmNavigationRequest.ConfirmNavigationRequest alt 拒绝 Nav-->>Nav: NavigationResult.Success = false else 允许 Nav->>From: INavigationAware.OnNavigatedFrom Nav->>To: 解析或复用 IsNavigationTarget Nav->>To: INavigationAware.OnNavigatedTo Nav->>Nav: 写入 Journal end ``` | 接口 | 方法 | 时机 | | --- | --- | --- | | `IConfirmNavigationRequest` | `ConfirmNavigationRequest(ctx, continuation)` | 离开前;`continuation(false)` 取消。继承 `INavigationAware` | | `INavigationAware` | `OnNavigatedFrom` | 离开 | | `INavigationAware` | `IsNavigationTarget` | 区域内已有同类型实例时,是否复用 | | `INavigationAware` | `OnNavigatedTo` | 进入;从 `NavigationContext.Parameters` 取参 | | `IJournalAware` | `PersistInHistory()` | 返回 false 则不写入前进/后退日志 | | `IDestructible` | `Destroy()` | 视图从区域移除时 | ```csharp public class ViewAViewModel : BindableBase, INavigationAware, IConfirmNavigationRequest { public void OnNavigatedTo(NavigationContext navigationContext) { var id = navigationContext.Parameters.GetValue("id"); } public bool IsNavigationTarget(NavigationContext navigationContext) => true; public void OnNavigatedFrom(NavigationContext navigationContext) { } public void ConfirmNavigationRequest(NavigationContext navigationContext, Action continuation) { continuation(!HasUnsavedChanges); } } ``` `NavigationContext` 含 `Uri`、`Parameters`、`NavigationService`。`NavigationResult` 含 `Result`(成功/取消)和 `Error`。 ### 导航日志(Journal) 每个 `IRegion` 有 `NavigationService.Journal`(`IRegionNavigationJournal`): ```csharp var journal = region.NavigationService.Journal; if (journal.CanGoBack) journal.GoBack(); if (journal.CanGoForward) journal.GoForward(); journal.Clear(); ``` 可把后退/前进绑成 `DelegateCommand`,用 `ObservesProperty` 观察 `CanGoBack`(若自行包装通知)。 --- ## 10. 对话框(Dialog) 对话框是「带宿主窗口的导航视图」:内容来自注册的 UserControl,外壳是 `IDialogWindow`。 ### 注册 ```csharp containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog("Notify"); containerRegistry.RegisterDialogWindow(); // 替换默认宿主 containerRegistry.RegisterDialogWindow("AnotherDialogWindow"); ``` 也可在对话框 View / 窗口上标 `[Dialog]` / `[DialogWindow]`,再 `RegisterGenerated()`,详见 [源生成器](#3-源生成器)。 宿主必须实现 [`IDialogWindow`](src/Wpf/Prism.Wpf/Services/Dialogs/IDialogWindow.cs): ```csharp public partial class CustomDialogWindow : Window, IDialogWindow { public IDialogResult Result { get; set; } } ``` ### IDialogAware ViewModel **必须**实现: ```csharp public interface IDialogAware { string Title { get; } event Action? RequestClose; bool CanCloseDialog(); void OnDialogClosed(); void OnDialogOpened(IDialogParameters parameters); } ``` | 成员 | 时机 | | --- | --- | | `OnDialogOpened` | 窗口显示前,读参数 | | `Title` | 同步到窗口标题 | | `RequestClose` | ViewModel 请求关闭;参数为 `IDialogResult` | | `CanCloseDialog` | 用户点关闭或 `RequestClose` 时;返回 false 阻止 | | `OnDialogClosed` | 关闭后清理 | ### 显示 [`IDialogService`](src/Wpf/Prism.Wpf/Services/Dialogs/IDialogService.cs): ```csharp dialogService.ShowDialog("NotificationDialog", parameters, result => { if (result.Result == ButtonResult.OK) { } }); dialogService.Show("NotificationDialog", parameters, result => { }); // 非模态 dialogService.ShowDialog("NotificationDialog", parameters, callback, "AnotherDialogWindow"); ``` 扩展方法(`IDialogServiceExtensions`)提供无参数、无回调的简化重载。异步包装在 `IDialogServiceAsyncExtensions`: ```csharp IDialogResult result = await dialogService.ShowDialogAsync("NotificationDialog", parameters); await dialogService.ShowAsync("NotificationDialog", windowName: "AnotherDialogWindow"); ``` `ShowDialogAsync` 在 WPF 上仍走 `Window.ShowDialog()`,会占用 UI 线程直到关闭。Avalonia 没有 WPF 那种同步嵌套消息循环:`IDialogWindow.ShowDialog(Window)` 返回 `Task`,平台 `IDialogService.ShowDialog` 仍然是带 Closed 回调的 `void`;`ShowDialogAsync` 在 Composition 层包一层回调,Avalonia 可直接用,**不会**阻塞 UI 线程。`CancellationToken` 只取消返回的 `Task`,**不会**关闭已经打开的窗口。`parameters` 为 null 时使用空的 `DialogParameters`。`IDialogService` 的四个 `void` 方法保持不变。 `ButtonResult`:`None`、`OK`、`Cancel`、`Abort`、`Retry`、`Ignore`、`Yes`、`No`。 ```csharp RequestClose?.Invoke(new DialogResult(ButtonResult.OK, new DialogParameters { { "selected", item } })); ``` 关闭后从 `IDialogResult.Parameters` 取返回值。 ### 窗口样式与位置 对话框内容上可用 [`Dialog`](src/Wpf/Prism.Wpf/Services/Dialogs/Dialog.cs) 的附加属性控制宿主: | 附加属性 | 作用 | | --- | --- | | `prism:Dialog.WindowStyle` | 宿主 `Window` 的 Style(无边框、ToolWindow 等) | | `prism:Dialog.WindowStartupLocation` | 打开位置(如 `CenterOwner`) | `DialogService` 会:解析宿主 → 解析内容 → `AutowireViewModel` → 校验 `IDialogAware` → 设置 `Content`/`DataContext` → `Show` 或 `ShowDialog`。 --- ## 11. XAML 交互(Interactivity) [`InvokeCommandAction`](src/Wpf/Prism.Wpf/Interactivity/InvokeCommandAction.cs) 基于 `Microsoft.Xaml.Behaviors`,把任意事件接到 `ICommand`: ```xml xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:prism="http://prismlibrary.com/" ``` | 属性 | 作用 | | --- | --- | | `Command` | 要执行的命令 | | `CommandParameter` | 优先作为命令参数;否则用事件参数 | | `AutoEnable` | 默认 true,按 `CanExecute` 启用/禁用关联控件 | | `TriggerParameterPath` | 从事件参数上按 `.` 路径取值作为命令参数 | `CommandBehaviorBase` 是底层实现,一般不直接使用。 --- ## 12. 生命周期接口 这些接口可打在 **View 或 ViewModel** 上,Region Behavior / 导航服务会同时检查两者。 | 接口 | 命名空间 | 作用 | | --- | --- | --- | | `IActiveAware` | `Prism` | `IsActive` + `IsActiveChanged`;区域激活、`CompositeCommand(monitorCommandActivity)` | | `IDestructible` | `Prism.Navigation` | `Destroy()`;区域移除时释放资源 | | `IRegionMemberLifetime` | `Prism.Regions` | `KeepAlive`;停用是否保留实例 | | `INavigationAware` | `Prism.Regions` | 导航进入/离开/是否复用 | | `IConfirmNavigationRequest` | `Prism.Regions` | 导航前确认 | | `IJournalAware` | `Prism.Regions` | 是否写入导航日志 | | `IDialogAware` | `Prism.Services.Dialogs` | 对话框生命周期 | | `IDialogWindow` | `Prism.Services.Dialogs` | 自定义对话框宿主 | --- ## 13. 日志(Serilog) Prism 8 起不再提供 `ILoggerFacade`。本仓库用 **`Microsoft.Extensions.Logging.ILogger`** 作为应用侧 API,由独立包 [`Prism.Logging.Serilog`](src/Logging/Prism.Logging.Serilog) 或 [`Prism.Logging.NLog`](src/Logging/Prism.Logging.NLog) 接入日志后端。`Prism.Core` / `Prism.Wpf` 不引用这两种后端。 不要再包一层 Prism 自己的日志接口,也不要使用已过时的社区 `ILoggerFacade` 适配器。 ### Serilog:登记内容 `RegisterSerilog` 一次登记: | 服务 | 生命周期 | 实现 | | --- | --- | --- | | `Serilog.ILogger` | Instance | 传入的 logger / `Log.Logger` | | `ILoggerFactory` | Instance | `SerilogLoggerFactory` | | `ILogger<>` | Singleton 开泛型 | MEL `Logger` | | `ILogger` | Singleton | `CreateLogger("Default")` | 业务代码注入 `ILogger`。需要 `ForContext` 时再注入 `Serilog.ILogger`。 三个重载: ```csharp containerRegistry.RegisterSerilog(); // 使用已配置的 Log.Logger containerRegistry.RegisterSerilog(logger); // 指定 Serilog.ILogger containerRegistry.RegisterSerilog(cfg => cfg.WriteTo.File("app.log")); // 用回调构建 ``` 无参重载在 `Log.Logger` 仍是 Silent/`Logger.None` 时抛 `InvalidOperationException`,避免无 sink 静默丢日志。传入的 logger 会赋给 `Log.Logger`,静态 `Log.xxx` 与 DI 走同一管道。`dispose` 默认 `false`:本仓库退出时不 Dispose 容器,由宿主 `Log.CloseAndFlush()` 刷新。 ### 启动时序 在 `base.OnStartup` **之前**配置 Serilog。`Initialize()` 里模块加载、解析 Shell 都发生在 `RegisterTypes` 前后,启动失败只能靠静态 `Log.Logger`。 ```csharp protected override void OnStartup(StartupEventArgs e) { Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.File("logs/app.log", outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}") .CreateLogger(); try { base.OnStartup(e); } catch (Exception ex) { Log.Fatal(ex, "Application failed to start"); throw; } } protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterSerilog(); } protected override void OnExit(ExitEventArgs e) { Log.CloseAndFlush(); base.OnExit(e); } ``` 输出模板应包含 `{SourceContext}`,对应 `ILogger` 的类别(类型全名)。Sink、`appsettings.json`、Enricher 留给宿主,本包不捆绑。 ### 在 ViewModel 中记录 ```csharp public MainWindowViewModel( IDialogService dialogService, IRegionManager regionManager, ILogger logger) { _logger = logger; } [DelegateCommand] private void Navigate() { _logger.LogInformation("Navigating to {View}", NavigationNames.ViewA); _regionManager.RequestNavigate("ContentRegion", NavigationNames.ViewA); } ``` 模块只需引用 `Microsoft.Extensions.Logging.Abstractions`,不必引用 Serilog。HelloWorld 的 [`MainWindowViewModel`](e2e/Wpf/HelloWorld/ViewModels/MainWindowViewModel.cs) 与 [`ViewAViewModel`](e2e/Wpf/Modules/HelloWorld.Modules.ModuleA/ViewModels/ViewAViewModel.cs) 已按此注入;日志写入 `logs/helloworld.log` 与 VS 调试输出。 ### NLog:安装与登记 应用安装 `Prism.Logging.NLog` 后,在 `RegisterTypes` 中调用 `RegisterNLog`。它登记 NLog 的 `LogFactory`、默认类别的 `NLog.ILogger`,以及 MEL 的 `ILoggerFactory`、`ILogger` 和 `ILogger`。业务代码仍注入 `ILogger`;需要 NLog 原生 API 时可注入 `LogFactory` 或 `NLog.ILogger`。 ```powershell dotnet add package Prism.Logging.NLog ``` ```csharp using NLog; using NLog.Config; using NLog.Targets; using Prism.Ioc; // 方式一:先配置全局工厂,再注册。 var configuration = new LoggingConfiguration(); configuration.AddRuleForAllLevels(new FileTarget("file") { FileName = "logs/app.log" }); LogManager.Configuration = configuration; containerRegistry.RegisterNLog(); // 方式二:传入已配置的独立 LogFactory。 var isolatedConfiguration = new LoggingConfiguration(); isolatedConfiguration.AddRuleForAllLevels(new FileTarget("file") { FileName = "logs/app.log" }); var logFactory = new LogFactory { Configuration = isolatedConfiguration }; containerRegistry.RegisterNLog(logFactory); // 方式三:由回调配置并创建独立 LogFactory。 containerRegistry.RegisterNLog(config => config.AddRuleForAllLevels(new FileTarget("file") { FileName = "logs/app.log" })); ``` 以上是三种替代用法,同一应用选一种即可。无参重载要求全局工厂已有配置;显式工厂也必须已有配置,否则抛 `InvalidOperationException`。空工厂或空回调抛 `ArgumentNullException`。后两种方式不修改 `LogManager` 的全局配置。`shutdownOnDispose` 默认 `false`,由宿主管理 NLog 的关闭;设为 `true` 时,释放已注册的 MEL `ILoggerFactory` 会关闭对应的 NLog 工厂。 --- ## 14. 应用服务(Prism.Wpf.Application) 跨平台接口在 [`Prism.Application.Abstractions`](src/Application/Prism.Application.Abstractions)(不引用其他项目,不含实现);WPF 实现在 [`Prism.Wpf.Application`](src/Wpf/Prism.Wpf.Application)。应用侧引用后者即可(会传递依赖前者)。`Prism.DryIoc` **不**传递依赖这些包;WPF 应用显式调用 `RegisterApplicationServices()`。不改 `PrismApplicationBase` 启动顺序,也不把 UI 服务放进 `Prism.Core`。调度 / 消息框 / 文件框的 Avalonia 适配留给后续 `Prism.Avalonia.Application`。 ### 登记 在 `RegisterTypes` 里、`RegisterSerilog` 之后: ```csharp containerRegistry.RegisterConfiguration(configuration); // 可选 containerRegistry.RegisterApplicationServices(); ``` `RegisterApplicationServices` 一次登记: | 服务 | 生命周期 | 实现 | | --- | --- | --- | | `IDispatcher` | Singleton | `DispatcherService`(`Application.Current.Dispatcher`) | | `IBusyService` / `IBusyIndicator` | 同一 Singleton | `BusyService`(引用计数) | | `IMessageService` | Singleton | `DialogMessageService` | | `IExceptionHandler` | Singleton | `DefaultExceptionHandler` | | `IFileDialogService` | Singleton | `FileDialogService` | | 对话框 `PrismMessageDialog` / `PrismConfirmationDialog` | 按名 | 仅在尚未 `IsRegistered` 时登记 | ### 启动时序 异常挂钩必须在 `base.OnStartup` **之前**订阅,容器尚未就绪时只写 `Trace`,不弹窗: ```csharp protected override void OnStartup(StartupEventArgs e) { HelloWorldLogging.Configure(); HelloWorldConfiguration.Load(); this.UseApplicationExceptionHandling(); try { base.OnStartup(e); } catch (Exception ex) { Log.Fatal(ex, "Application failed to start"); throw; } } ``` ### 消息与默认对话框名 `IMessageService.Show` / `Confirm` 走第 10 节的同步 `ShowDialog`,并经 `IDispatcher.Invoke` 切回 UI,阻塞到对话框关闭。默认名是 `KnownDialogs.Message`(`PrismMessageDialog`)与 `KnownDialogs.Confirmation`(`PrismConfirmationDialog`),避免和示例里的 `NotificationDialog` 抢名。应用可用自己的 `IDialogAware` 视图按同名覆盖。两个名字都未登记时回退 `MessageBox`。 ### 忙碌、配置、文件框 - `IBusyService.Begin` / `Run` 只做引用计数,**不** `Task.Run`,也**不**改命令基础设施。HelloWorld 主窗用 `{prism:ContainerProvider {x:Type svc:IBusyIndicator}}` 绑遮罩。 - `RegisterConfiguration` 只 `RegisterInstance`。选项请在应用侧 `config.GetSection("Ui").Get()` 后再 `RegisterInstance`;本包不自造 `IOptions`。 - `IFileDialogService` 封装 Win32 Open/Save 与文件夹选择。 ### 明确不做 - Generic Host 与 DryIoc / `IServiceCollection` 双向映射 - AvalonDock 等 Region Adapter - `IAuthorizationService` / 登录壳 - 让 Core 命令感知忙碌状态 - Avalonia 适配(留给后续 `Prism.Avalonia.Application`) --- ## 示例用法 仓库内可运行示例在 [`e2e/Wpf`](e2e/Wpf),通过 **ProjectReference** 直接引用 `src/`。下面按真实文件说明如何把上述功能串起来。事件聚合、按需模块、导航日志在 HelloWorld 中没有演示,可按上文 API 自行接入。Serilog 接入见 [`HelloWorldLogging.cs`](e2e/Wpf/HelloWorld/HelloWorldLogging.cs) 与 `RegisterSerilog()`。应用服务见 `RegisterApplicationServices()`、[`appsettings.json`](e2e/Wpf/HelloWorld/appsettings.json) 与主窗忙碌遮罩。 ### 用 PrismApplication 启动 [`e2e/Wpf/HelloWorld/App.xaml`](e2e/Wpf/HelloWorld/App.xaml): ```xml ``` [`App.xaml.cs`](e2e/Wpf/HelloWorld/App.xaml.cs): ```csharp public partial class App { protected override void OnStartup(StartupEventArgs e) { HelloWorldLogging.Configure(); HelloWorldConfiguration.Load(); this.UseApplicationExceptionHandling(); try { base.OnStartup(e); } catch (Exception ex) { Log.Fatal(ex, "Application failed to start"); throw; } } protected override void OnExit(ExitEventArgs e) { Log.CloseAndFlush(); base.OnExit(e); } protected override Window CreateShell() { return Container.Resolve(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterSerilog(); containerRegistry.RegisterConfiguration(HelloWorldConfiguration.Current); containerRegistry.RegisterApplicationServices(); containerRegistry.RegisterSharedSamples(); } protected override IModuleCatalog CreateModuleCatalog() { return new DirectoryModuleCatalog() { ModulePath = "Modules" }; } } ``` 同文件注释了 `ConfigurationModuleCatalog` 与 `JsonModuleCatalog("modules.json")` 的切换方式。 ### 注册对话框与自定义窗口 对话框 View 与自定义宿主窗口用特性标记,例如 [`NotificationDialog.xaml.cs`](e2e/Wpf/HelloWorld/Dialogs/NotificationDialog.xaml.cs)、[`CustomDialogWindow.xaml.cs`](e2e/Wpf/HelloWorld/Dialogs/CustomDialogWindow.xaml.cs)、[`AnotherDialogWindow.xaml.cs`](e2e/Wpf/HelloWorld/Dialogs/AnotherDialogWindow.xaml.cs): ```csharp [Dialog(typeof(NotificationDialogViewModel))] public partial class NotificationDialog : UserControl { } [Dialog(typeof(ConfirmationDialogViewModel))] public partial class ConfirmationDialog : UserControl { } [DialogWindow] public partial class CustomDialogWindow : Window, IDialogWindow { } [DialogWindow(nameof(AnotherDialogWindow))] public partial class AnotherDialogWindow : Window, IDialogWindow { } ``` [`SharedSampleRegistrations.cs`](e2e/Wpf/HelloWorld/SharedSampleRegistrations.cs) 只调用生成的扩展: ```csharp public static void RegisterSharedSamples(this IContainerRegistry containerRegistry) { containerRegistry.RegisterGenerated(); } ``` 无参 `[DialogWindow]` 覆盖默认宿主;带名称的版本在 `ShowDialog(..., windowName)` 时选用。 ### 区域导航 [`Views/MainWindow.xaml`](e2e/Wpf/HelloWorld/Views/MainWindow.xaml): ```xml