在 WPF 项目中引入 Prism 框架后,第一个映入眼帘的通常是自动生成的 App.xaml.cs,里面只有寥寥几行代码。然而,这几行代码背后隐藏着整个应用程序的启动脉络。理解它们,是掌握 Prism 开发的第一步。

1. 默认代码长什么样?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return new Shell();
}

protected override void RegisterTypes(Prism.Ioc.IContainerRegistry containerRegistry)
{
// 目前是空的
}
}

App​ 类继承自 PrismApplication​(而非默认的 Application),这意味着启动流程被 Prism 接管了。我们只需重写两个关键方法,框架就会按部就班地工作。


2. 完整的启动时间线

当程序运行,Main 入口被调用后,Prism 执行以下步骤(这些步骤都在基类中,我们看不到,但必须知晓):

  1. 创建依赖注入容器(默认使用 Dryloc 或 Unity)。
  2. 调用 RegisterTypes —— 你可以在这里注册自己的服务(接口与实现类的映射)。
  3. 调用 CreateShell​ —— 获取主窗口实例(这里只是 new,尚未显示)。
  4. 自动解析 ViewModel —— Prism 会利用容器构造 Shell​ 对应的 ShellViewModel​(如果存在),并自动注入到 Shell 的构造函数中。
  5. 显示主窗口 —— 基类调用 shell.Show(),应用程序正式启动。

3. 两个重写方法的深度解读

3.1 CreateShell —— 告诉框架“谁是主窗口”

1
2
3
4
protected override Window CreateShell()
{
return new Shell();
}
  • 作用:返回应用程序的主窗口实例。
  • 注意:这里只负责 创建视图(View) ,不要在这里编写业务逻辑,也不要手动 new ViewModel。ViewModel 会由容器通过构造函数自动注入。
  • 常见疑问:为什么没有 Show()?因为显示由基类统一管理,你只需要返回实例。

3.2 RegisterTypes —— 依赖注入的“注册中心”

1
2
3
4
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// 目前是空的
}
  • 作用:注册所有需要注入的服务。例如,如果你的 ShellViewModel​ 需要 IOrderService,你就必须在这里注册:

    1
    containerRegistry.Register<IOrderService, OrderService>();
  • 支持的生命周期

    • Register(瞬时,每次请求新建)
    • RegisterSingleton(单例)
    • RegisterScoped(WPF 中较少用)
  • 空注册为什么能运行? 如果 Shell​ 和 ShellViewModel​ 都没有任何构造参数,或者你在 XAML 中直接设置了 DataContext,那么注册表为空也不会报错。


4. 依赖注入与自动装配 —— Prism 的“黑魔法”

Prism 的 ViewModelLocator​ 会自动按照命名约定将 View 与 ViewModel 绑定(例如 Shell​ 对应 ShellViewModel)。这要求:

  • ViewModel 必须放在合适的命名空间(通常为 Views​ 同名或通过 ViewModelLocationProvider 配置)。
  • ViewModel 的构造函数可以接收任何已注册的服务接口,容器会在创建时自动提供实现。

示例

1
2
3
4
5
6
7
public class ShellViewModel
{
public ShellViewModel(IOrderService orderService, ILogger logger)
{
// 容器会自动把注册过的 IOrderService 和 ILogger 传进来
}
}

5. 新手最容易踩的三个“坑”

① 程序闪退,没有任何错误弹窗

绝大多数是因为 构造函数中有未注册的服务。Prism 在解析时会抛出 ResolutionFailedException​,但由于是启动时异常,往往直接终止进程。
排查方法:在 Visual Studio 中勾选“CLR 异常”的所有 throw,然后调试运行,就能定位到具体哪个类型无法解析。

② 在 CreateShell​ 里写了 new ShellViewModel(...)

这破坏了依赖注入的理念,导致容器无法管理 ViewModel 的生命周期,也不利于单元测试。正确做法是只 return new Shell();,让容器自动装配。

③ 想延迟显示窗口,却在 CreateShell 里写等待逻辑

应该重写 OnInitialized​ 方法,在这里执行初始化代码(如加载配置、登录验证),最后调用 base.OnInitialized(),它会自动显示 Shell。

1
2
3
4
5
protected override void OnInitialized()
{
// 这里在窗口显示之前执行
base.OnInitialized(); // 这一行会触发 Shell 显示
}

6. 还可以重写哪些有用的方法?

除了上述两个,PrismApplication 还提供了多个可重写的虚方法,便于扩展:

  • ConfigureModuleCatalog —— 配置模块加载(用于 Prism 的模块化开发)。
  • ConfigureViewModelLocator —— 自定义 ViewModel 定位规则。
  • RegisterRequiredTypes(内部) —— 已注册 Prism 必需的类型,一般不需要重写。
  • OnStartup​ —— 如果你需要更早介入,可以重写它,但务必调用 base.OnStartup(e)

7. 总结一句话

RegisterTypes是“准备食材”(注册服务),CreateShell是“拿出主锅”(创建主窗口),Prism 框架负责“开火做饭”(构建依赖并显示窗口)。

掌握这个启动逻辑,你就能从容地构建 Prism 应用,并享受依赖注入带来的松耦合和可测试性。后续深入学习模块化、导航、事件聚合等高级特性时,这个基础认知会一直陪伴你。