























首先我们了解一下插件这个概念
插件(add-in,也称为plug-in)是应用程序能够动态发现、加载和使用的单独编译过的组件。
通常,应将应用程序设计成能使用插件,从而可在将来进行增强,而不必进行任何修改、重新编译以及重新测试。
插件还为针对特殊的市场或客户单独定制应用程序实例提供了灵活性。但使用插件模型最常见的原因是,允许第三方开发人员扩展应用程序的功能。
例如,AdobePhotoshop中的插件提供了大量图片处理效果,类似Camera Raw。Google Chrome中的插件提供了增强的Web 冲浪特性以及全新功能。对于这两种情况,插件都是由第三方开发人员创建的。
.NET提供了MAF和MEF两种框架可以实现插件化。
MAF:https://learn.microsoft.com/en-us/dotnet/desktop/wpf/app-development/wpf-add-ins-overview
MEF:https://learn.microsoft.com/zh-cn/dotnet/framework/mef/
Prism的模块化原理就类似插件模型。它也能够动态地发现、加载和使用单独编译的组件。
在前面介绍Prism框架时,提到模块化的作用。模块化拆分、隔离与复用,核心目标都是降低应用耦合度,提升可维护性。
1、每个模块负责特定功能(如用户管理、订单管理),实现 “高内聚、低耦合”,便于团队协作、维护和迭代。
2、模块之间可以做到低依赖(减少直接引用,通过依赖注入实现复用),单个模块可独立编译、测试,甚至在多个应用中复用。
3、最终都能将多个独立模块整合到同一个主应用中,形成完整的功能体系,而非零散的组件。

就拿医疗行业的软件来说,它可能会划分如上的模块。不同的模块可以单独开发,测试。最后再将它们一起”组装“到”外壳“上。
对于一些基础模块,通常会定义成公用模块。需要使用的子模块可以直接引用这些基础模块。
注意:这里我们的目的是做到模块间低依赖,而不是完全没有依赖。
但是Prism的模块化和插件化会有稍许区别
模块化是 “核心架构手段”,最终目的是 “大型应用的结构化拆分与管理”。
Prism 希望在应用开发初期,就将应用拆分为多个模块,每个模块遵循统一的规范(IModule),主应用通过明确配置加载模块,便于团队分工、版本控制和后期维护。
简单来说:Prism 的模块化是为了 “事前规划”。
最终目的是 “可扩展性”(插件化)。
当主应用发布后,无需修改主程序代码、无需重新编译,只需将新的模块(插件)放入指定目录,主应用就能自动识别并加载该模块,实现功能扩展。
简单说:插件化是为了 “事后扩展”。
有些小伙伴可能对这个模块的划分还存在一些困惑。
这里介绍 一下模块划分的原则
1、高内聚:一个模块只负责完成单一且明确的业务功能,不包含无关逻辑。
2、低耦合:模块之间尽量减少直接依赖,禁止跨模块直接引用View、ViewModel 或内部业务类。
3、创建独立的基础设施类库(CommonInfrastructure 项目),该项目仅包含公共接口、枚举、实体(DTO/POCO,无业务逻辑)。
这个基础设备类库是模块间交互的核心
在Prism中涉及两种模块化的加载,一种是配置式,一种是扫描式。
配置式是显式配置,也就是主动指定要加载的模块。扫描式是隐式配置,也就是对目录进行扫描,然后自动添加符合要求的模块。
模块加载这里主要用到的是.NET的反射机制。
假设我定义了一个IModule接口(仅演示用,所以未定义具体的接口函数)
1 public interface IModule 2 { 3 4 5 }
然后我定义了一个类库LibA,增加一个类ModuleAModule继承自IModule接口
1 public class ModuleAModule : IModule 2 { 3 4 }
再定义一个类库LibB,增加一个类ModuleBModule
1 public class ModuleBModule 2 { 3 4 }

当我们去扫描这两个dll,通过判断是否实现了IModule接口,就可以判断是否是我们定义的模块。
简易验证过程如下:
Type.GetInterface(typeof(IModule).FullName)验证该类型是否实现了IModule。class、是否非抽象(type.IsClass && !type.IsAbstract)。示例代码如下:
1 public List<Type> FindAllIModuleTypes(string moduleDirectory) 2 { 3 var moduleTypes = new List<Type>(); 4 var assemblyFiles = Directory.GetFiles(moduleDirectory, "*.dll", SearchOption.TopDirectoryOnly); 5 6 foreach (var assemblyFile in assemblyFiles) 7 { 8 try 9 { 10 // 1. 加载程序集 11 Assembly assembly = Assembly.LoadFrom(assemblyFile); 12 13 // 2. 遍历程序集中的所有类型 14 foreach (Type type in assembly.GetTypes()) 15 { 16 // 3. 核心筛选:识别 IModule 实现类 17 bool isValidModule = typeof(IModule).IsAssignableFrom(type) && // 实现 IModule 18 type.IsClass && // 是类 19 !type.IsAbstract; // 非抽象 20 21 if (isValidModule) 22 { 23 moduleTypes.Add(type); 24 } 25 } 26 } 27 catch 28 { 29 // 跳过无法加载或无有效模块的程序集 30 continue; 31 } 32 } 33 34 return moduleTypes; 35 }
首先我们了解一下IModule接口,在Prism中,可以被加载的模块都需要实现Prism.Modularity.IModule接口。
IModule接口定义如下:
1 // 2 // 摘要: 3 // 为Prism应用程序中部署的模块定义契约。 4 public interface IModule 5 { 6 // 7 // 摘要: 8 // 用于向容器注册应用程序将使用的类型。 9 void RegisterTypes(IContainerRegistry containerRegistry); 10 11 // 12 // 摘要: 13 // 通知模块它已被初始化。 14 void OnInitialized(IContainerProvider containerProvider); 15 }
IModule提供了两个接口函数:RegisterTypes和OnInitialized,可以把它理解为模块的Bootstrapper。
这里我们以一个简易的信息登记功能为例,增加一个Register模块,一个ViewList模块,一个CommonModule公共模块。
Register模块:登记Employee信息
ViewList模块:查看登记的Employee列表
CommonModule公共模块:定义Employee及通信事件
当在Register模块登记一个Employee后,通过ViewModel通信,将Employee添加到ViewList模块的列表中。
整体框架如下:

这个模块主要是用于定义Employee及通信事件。
SelectEmployeeEvent.cs
1 public class SelectEmployeeEvent : PubSubEvent<IEmployeeViewModel> 2 { 3 }
IEmployeeViewModel.cs
1 public interface IEmployeeViewModel 2 { 3 int Id { get; set; } 4 5 string Name { get; set; } 6 7 string Phone { get; set; } 8 9 void XXX(); 10 }

注意:模块类在命名时,尽量以Module结尾,而且放在项目的根目录下,这样方便后期区分和查找

RegisterModule.cs
1 public class RegisterModule : Prism.Modularity.IModule 2 { 3 public void OnInitialized(IContainerProvider containerProvider) 4 { 5 6 } 7 8 public void RegisterTypes(IContainerRegistry containerRegistry) 9 { 10 11 } 12 }
这样我们就拥有了一个可以被Prism识别的模块
接下来我们对这个模块进行完善
注意:类库工程在添加View时,会找不到WPF项。

需要修改项目工程文件,添加UseWPF

添加Views\Register.xaml
这个页面用于登记
1 <Grid> 2 <Grid.RowDefinitions> 3 <RowDefinition/> 4 <RowDefinition/> 5 <RowDefinition/> 6 <RowDefinition/> 7 </Grid.RowDefinitions> 8 9 <Grid.ColumnDefinitions> 10 <ColumnDefinition/> 11 <ColumnDefinition/> 12 </Grid.ColumnDefinitions> 13 14 <Label Content="Id" HorizontalAlignment="Right" Margin="0,0,30,0" VerticalAlignment="Center"></Label> 15 <TextBox Grid.Row="0" Grid.Column="1" Margin="40,0" VerticalAlignment="Center" Text="{Binding EmployeeViewModel.Id}"></TextBox> 16 17 <Label Content="Name" Grid.Row="1" HorizontalAlignment="Right" Margin="0,0,30,0" VerticalAlignment="Center"></Label> 18 <TextBox Grid.Row="1" Grid.Column="1" Margin="40,0" VerticalAlignment="Center" Text="{Binding EmployeeViewModel.Name}"></TextBox> 19 20 <Label Content="Phone" Grid.Row="2" HorizontalAlignment="Right" Margin="0,0,30,0" VerticalAlignment="Center"></Label> 21 <TextBox Grid.Row="2" Grid.Column="1" Margin="40,0" VerticalAlignment="Center" Text="{Binding EmployeeViewModel.Phone}"></TextBox> 22 23 <Button Content="登记" Width="88" Height="28" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="3" Grid.ColumnSpan="2" Command="{Binding RegisterCommand}"></Button> 24 </Grid>
添加ViewModels\RegisterViewModel.cs
1 public class RegisterViewModel :BindableBase 2 { 3 private IEventAggregator eventAggregator; 4 5 public DelegateCommand RegisterCommand { get; private set; } 6 7 //EmployeeViewModel的定义请到文末示例代码中查看 8 private EmployeeViewModel employeeViewModel = new EmployeeViewModel(); 9 10 public EmployeeViewModel EmployeeViewModel 11 { 12 get => this.employeeViewModel; 13 set => this.SetProperty(ref employeeViewModel, value); 14 } 15 16 public RegisterViewModel(IEventAggregator eventAggregator) 17 { 18 RegisterCommand = new DelegateCommand(Register); 19 20 this.eventAggregator = eventAggregator; 21 } 22 23 private void Register() 24 { 25 //点击登记时,发送消息到ViewList模块 26 this.eventAggregator.GetEvent<SelectEmployeeEvent>().Publish(this.EmployeeViewModel); 27 } 28 }
此时我们再回到RegisterModule类,在RegisterTypes中注册View
1 public class RegisterModule : Prism.Modularity.IModule 2 { 3 public void OnInitialized(IContainerProvider containerProvider) 4 { 5 6 } 7 8 public void RegisterTypes(IContainerRegistry containerRegistry) 9 { 10 //注册View的时候同步绑定ViewModel 11 containerRegistry.RegisterForNavigation<RegisterView, RegisterViewModel>(); 12 13 //也可以只注册View 14 //然后通过ViewModelLocator.AutoWireViewModel附加属性来自动绑定ViewModel 15 //containerRegistry.RegisterForNavigation<RegisterView>(); 16 } 17 }
这样我们就拥有了一个完整的登记模块
然后我们按照这个逻辑,再添加ViewList模块
ViewListModule.cs
1 public class ViewListModule : IModule 2 { 3 public void OnInitialized(IContainerProvider containerProvider) 4 { 5 6 } 7 8 public void RegisterTypes(IContainerRegistry containerRegistry) 9 { 10 //注册视图 11 containerRegistry.RegisterForNavigation<EmployeeListView, EmployeeListViewModel>(); 12 } 13 }
Views\EmployeeListView.xaml
1 <Grid> 2 <ListBox ItemsSource="{Binding EmployeeList}"> 3 <ListBox.ItemTemplate> 4 <DataTemplate> 5 <Grid> 6 <Grid.RowDefinitions> 7 <RowDefinition/> 8 <RowDefinition/> 9 </Grid.RowDefinitions> 10 11 <TextBlock Text="{Binding Name}" HorizontalAlignment="Left" Margin="10,0,0,0" FontWeight="Bold" FontSize="13" Foreground="Blue"></TextBlock> 12 <DockPanel Grid.Row="1" Margin="0,5,0,0"> 13 <TextBlock Text="Id:" Margin="10,0,5,0"></TextBlock> 14 <TextBlock Text="{Binding Id}" Margin="5,0,10,0"></TextBlock> 15 <TextBlock Text="Phone:" Margin="10,0,5,0"></TextBlock> 16 <TextBlock Text="{Binding Phone}" FontStyle="Italic"></TextBlock> 17 </DockPanel> 18 </Grid> 19 </DataTemplate> 20 </ListBox.ItemTemplate> 21 </ListBox> 22 </Grid>
ViewModels\EmployeeListViewModel.cs
1 public class EmployeeListViewModel : BindableBase 2 { 3 //列表 4 private ObservableCollection<IEmployeeViewModel> employeeList = new ObservableCollection<IEmployeeViewModel>(); 5 6 public ObservableCollection<IEmployeeViewModel> EmployeeList 7 { 8 get => this.employeeList; 9 set => this.SetProperty(ref employeeList, value); 10 } 11 12 public EmployeeListViewModel(IEventAggregator eventAggregator) 13 { 14 //创建登记界面发送数据订阅 15 eventAggregator.GetEvent<SelectEmployeeEvent>().Subscribe(OnAddEmployee); 16 } 17 18 //接收从登记界面发送过来的数据 19 private void OnAddEmployee(IEmployeeViewModel model) 20 { 21 this.EmployeeList.Add(model); 22 } 23 }
Prism模块加载流程如下图所示

这里我们了解一下IModuleCatalog接口和IModuleManager接口。
IModuleCatalog 是模块清单,负责 “记录” 应用中有哪些模块、加载规则和依赖关系,是静态的元数据存储;
1 /// <summary> 2 /// 这是ModuleManager的预期目录定义。ModuleCatalog包含应用程序可使用的模块信息。 3 /// 每个模块都在ModuleInfo类中进行了描述,该类记录了模块的名称、类型和位置。 4 /// </summary> 5 public interface IModuleCatalog 6 { 7 /// <summary> 8 /// 获取IModuleCatalog中的所有IModuleInfo类型 9 /// </summary> 10 IEnumerable<IModuleInfo> Modules { get; } 11 12 IEnumerable<IModuleInfo> GetDependentModules(IModuleInfo moduleInfo); 13 14 IEnumerable<IModuleInfo> CompleteListWithDependencies(IEnumerable<IModuleInfo> modules); 15 16 /// <summary> 17 /// 初始化目录,这可能会加载并验证模块。 18 /// </summary> 19 void Initialize(); 20 21 /// <summary> 22 /// 将IModuleInfo添加到IModuleCatalog中 23 /// </summary> 24 IModuleCatalog AddModule(IModuleInfo moduleInfo); 25 }
它会在Bootstrapper初始化时,进行初始化。过程如下所示:
1 public abstract class PrismApplicationBase : Application 2 { 3 protected override void OnStartup(StartupEventArgs e) 4 { 5 InitializeInternal(); 6 } 7 8 void InitializeInternal() 9 { 10 Initialize(); 11 } 12 13 protected virtual void Initialize() 14 { 15 _moduleCatalog = CreateModuleCatalog(); 16 } 17 18 protected virtual IModuleCatalog CreateModuleCatalog() 19 { 20 return new ModuleCatalog(); 21 } 22 }
IModuleManager 是模块加载器,负责 “执行” 模块的加载和初始化,支持立即加载和按需加载,是动态的操作组件;
1 /// <summary> 2 /// Defines the interface for the service that will retrieve and initialize the application's modules. 3 /// </summary> 4 public interface IModuleManager 5 { 6 /// <summary> 7 /// Gets all the <see cref="IModuleInfo"/> classes that are in the <see cref="IModuleCatalog"/>. 8 /// </summary> 9 IEnumerable<IModuleInfo> Modules { get; } 10 11 /// <summary> 12 /// Initializes the modules marked as <see cref="InitializationMode.WhenAvailable"/> on the <see cref="IModuleCatalog"/>. 13 /// </summary> 14 void Run(); 15 16 /// <summary> 17 /// Loads and initializes the module on the <see cref="IModuleCatalog"/> with the name <paramref name="moduleName"/>. 18 /// </summary> 19 /// <param name="moduleName">Name of the module requested for initialization.</param> 20 void LoadModule(string moduleName); 21 22 /// <summary> 23 /// Raised repeatedly to provide progress as modules are downloaded. 24 /// </summary> 25 event EventHandler<ModuleDownloadProgressChangedEventArgs> ModuleDownloadProgressChanged; 26 27 /// <summary> 28 /// Raised when a module is loaded or fails to load. 29 /// </summary> 30 event EventHandler<LoadModuleCompletedEventArgs> LoadModuleCompleted; 31 }
IModuleManager也是在Bootstrapper初始化时进行初始化,调用流程如下所示
1 public abstract class PrismApplicationBase : Application 2 { 3 protected override void OnStartup(StartupEventArgs e) 4 { 5 InitializeInternal(); 6 } 7 8 void InitializeInternal() 9 { 10 Initialize(); 11 } 12 13 protected virtual void Initialize() 14 { 15 RegisterRequiredTypes(_containerExtension); 16 } 17 18 protected virtual void RegisterRequiredTypes(IContainerRegistry containerRegistry) 19 { 20 //注入IModuleManager单例 21 containerRegistry.RegisterSingleton<IModuleManager, ModuleManager>(); 22 } 23 }
经过上述步骤,我们就创建好了两个Prism模块。接下来我们需要做的就是把它们加载到主窗口(Shell),然后一起工作。
Prism提供了5种加载模块的方法:
1、代码加载
2、目录扫描
3、手动加载
4、Xaml加载
5、AppConfig加载
这里的话只介绍前面三种方式,因为就我目前实际开发情况来看,后面两种方法用到的相对较少。
感兴趣的可以参考下面的代码自行学习:
https://github.com/PrismLibrary/Prism-Samples-Wpf/tree/master/07-Modules-AppConfig(AppConfig加载)
https://github.com/PrismLibrary/Prism-Samples-Wpf/tree/master/07-Modules-Xaml(Xaml加载)
实现步骤如下:

引用我们需要使用的Prism模块项目

1 protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) 2 { 3 //添加模块引用 4 moduleCatalog.AddModule<RegisterModule>(); 5 moduleCatalog.AddModule<ViewListModule>(); 6 }
1 protected override void OnInitialized() 2 { 3 base.OnInitialized(); 4 5 var regionManager = this.Container.Resolve<IRegionManager>(); 6 7 regionManager.RegisterViewWithRegion("EditArea", typeof(RegisterView)); 8 regionManager.RegisterViewWithRegion("ListArea", typeof(EmployeeListView)); 9 }
在前面我介绍介绍过,RegisterViewWithRegion函数可以绑定默认视图。
现在就等于将RegisterView绑定到EditArea区域,将EmployeeListView绑定到ListArea区域。
而RegisterView和EmployeeListView都分别来自于不同的模块。
程序运行效果如下:


可以看到,这些功能虽然不是在一个模块里定义的,但最终可以被组装到一起,实现一个完整的功能 。
目录扫描主要是通过指定一个目录,然后Prism框架自动去扫描目录中符合要求的Prism模块。
实现步骤如下:
例如我们将前面的两个模块输出到Modules目录下
Modules目录

项目输出路径

1 public partial class App : PrismApplication 2 { 3 protected override Window CreateShell() 4 { 5 return Container.Resolve<MainWindow>(); 6 } 7 8 protected override void RegisterTypes(IContainerRegistry containerRegistry) 9 { 10 11 } 12 13 protected override IModuleCatalog CreateModuleCatalog() 14 { 15 //指定目录 16 //可以使用相对路径也可以使用绝对路径 17 //推荐相对路径 18 return new DirectoryModuleCatalog() { ModulePath = @".\Modules" }; 19 } 20 }
说明:在功能模块明确的情况下,推荐使用代码加载方式加载模块。有时候我们想动态扩展一些功能,可以使用目录加载。例如我有一款设备,在出厂时只有A型号,后期可能会增加B、C型号,或者更多。
手动加载的整体流程和代码加载是一样的,都是需要引用项目工程。
在前面我们介绍了IModuleManager接口,它会在Bootstrapper初始化时,自动注入到容器中
手动加载模块主要用到IModuleManager接口。
实现步骤如下:
1 <Grid> 2 <Grid.RowDefinitions> 3 <RowDefinition Height="35"/> 4 <RowDefinition/> 5 </Grid.RowDefinitions> 6 7 <Grid Grid.Row="0"> 8 <Button Content="加载模块" HorizontalAlignment="Center" VerticalAlignment="Center" Width="88" Height="28" Command="{Binding ManualLoadModuleCommand}"/> 9 </Grid> 10 11 <Grid Grid.Row="1"> 12 <Grid.ColumnDefinitions> 13 <ColumnDefinition/> 14 <ColumnDefinition/> 15 </Grid.ColumnDefinitions> 16 17 <ContentControl prism:RegionManager.RegionName="EditArea"></ContentControl> 18 <ContentControl prism:RegionManager.RegionName="ListArea" Grid.Column="1"></ContentControl> 19 </Grid> 20 </Grid>

引用我们需要使用的Prism模块项目

1 public partial class App : PrismApplication 2 { 3 protected override Window CreateShell() 4 { 5 return Container.Resolve<MainWindow>(); 6 } 7 8 protected override void RegisterTypes(IContainerRegistry containerRegistry) 9 { 10 11 } 12 13 /// <summary> 14 /// 配置模块 15 /// </summary> 16 /// <param name="moduleCatalog"></param> 17 protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) 18 { 19 var moduleRegisterType = typeof(RegisterModule); 20 var moduleViewListType = typeof(ViewListModule); 21 moduleCatalog.AddModule(new ModuleInfo() 22 { 23 ModuleName = moduleRegisterType.Name, 24 ModuleType = moduleRegisterType.AssemblyQualifiedName, 25 InitializationMode = InitializationMode.OnDemand //需要时再加载 26 }); 27 moduleCatalog.AddModule(new ModuleInfo() 28 { 29 ModuleName = moduleViewListType.Name, 30 ModuleType = moduleViewListType.AssemblyQualifiedName, 31 InitializationMode = InitializationMode.OnDemand //需要时再加载 32 }); 33 } 34 }
这里我们在ViewModel中进行加载
1 /// <summary> 2 /// 手动加载模块 3 /// </summary> 4 private void ManualLoadModule() 5 { 6 this.moduleManager.LoadModule("RegisterModule"); 7 this.moduleManager.LoadModule("ViewListModule"); 8 }
运行效果

https://github.com/zhaotianff/WPF-MVVM-Beginner/tree/main/15_Prism_Module.sln
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。