模块化管理-Model
Module
是功能和资源的逻辑集合,其打包方式可以单独开发、测试、部署并集成到应用程序中。
当随着项目较为庞大,有大量的不同业务、View
、ViewModel
时,就需要考虑根据不同业务进行程序集的拆分了,而Module
就是帮助我们管理不同的程序集并集成到应用程序中。
每个模块化管理的程序集中都有一个模块核心类,IModule
的子类。主程序集通过加载这个IModule
的子类,就可以将对应程序集集成到主程序集的prism框架中来。
一、简单使用
这里以主程序使用其他模块管理的程序集中的Region
视图为例进行Module
的简单使用。
首先在主程序集所在的解决方案中添加新的WPF用户控件库、创建Views与ViewModels文件夹、引入prism.Unity
库。
①View层
在Views中添加将要使用用户控件ViewA
-
ViewA.xaml
<UserControl ......> <Grid> <StackPanel> <TextBlock Text="{Binding Value}"/> </StackPanel> </Grid> </UserControl>
②ViewModel层
在ViewModels中创建对应的视图模型ViewAViewModel
-
ViewAViewModel.cs
public class ViewAViewModel:BindableBase { private string _value = "这里是ViewA"; public string Value { get { return _value; } set { SetProperty(ref _value, value); } } }
③实现IModule
在程序集中创建SubModule实现IModule
接口,其内含以下两个函数:
OnInitialized(IContainerProvider containerProvider)
:当模块初始化时调用,可以处理一些业务逻辑。RegisterTypes(IContainerRegistry containerRegistry)
:进行类型注册,与主程序集中App后台代码的RegisterTypes
函数作用差不多的。
在RegisterTypes
函数中,通过IContainerRegistry
对象的RegisterForNavigation
方法注册ViewA类型。
在OnInitialized
函数中,进行区域的初始化加载,如果使用模块化管理,那么RegionView的初始化加载时机最好是放在Module中执行,这样的好处在于,当主程序无法顺利加载Module时,不会因此而抛出异常。而且思路跟整体性会较好。
-
SubModule.cs
public class SubModule : IModule { public void OnInitialized(IContainerProvider containerProvider) { //在这里进行区域的初始化加载是一个比较好的做法 var regionManager = containerProvider.Resolve<IRegionManager>(); regionManager.RegisterViewWithRegion("LeftMenuTreeRegion", typeof(TreeMenuView)); //LeftMenuTreeRegion是主程序中的Region名 //此外,模块初始化时可以在这里进行一些业务处理 } public void RegisterTypes(IContainerRegistry containerRegistry) { //进行类型注册,与主程序集中App的RegisterTypes函数作用差不多的。 //containerRegistry.RegisterForNavigation<Views.ViewA>(); 这里由于OnInitialized函数中使用了RegisterViewWithRegion方法来加载区域,所以不用再注册 } }
④加载Module
在主程序集的App后台代码中重写ConfigureModuleCatalog
方法,并通过该方法的参数IModuleCatalog
对象的AddModule
方法进行Module
类型的注册。
AddModule<T>()
:加载Module
类型,泛型T
为要加载的Module
类型。
-
App.xaml.cs
public partial class App : PrismApplication { ...... protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { moduleCatalog.AddModule<Sub.SubModule>(); } }
⑤使用视图
在主程序集中正常使用即可
-
MainWindow.xaml
<Window ...... xmlns:prism="http://prismlibrary.com/"> <Grid> <StackPanel> <Button Content="ModuleButton" Command="{Binding BtnCommand}"/> <ContentControl prism:RegionManager.RegionName="MainRegion"/> </StackPanel> </Grid> </Window>
-
MainWindowViewModel.cs
public class MainWindowViewModel:BindableBase { [Dependency] public IRegionManager regionManager { get; set; } public ICommand BtnCommand { get => new DelegateCommand(() => { regionManager.RequestNavigate("MainRegion", "ViewA"); }); } }
二、4种Module加载方式
1、AddModule的泛型方法
在主程序集的App后台代码中重写ConfigureModuleCatalog
方法,并通过该方法的参数IModuleCatalog
对象的AddModule<T>
方法进行Module
类型的加载。
AddModule<T>()
:加载Module
类型,泛型T为要加载的Module
类型。
上文例子中使用的就是这种方法,这里就不再举例了。
2、AddModule的非泛型方法
在主程序集的App后台代码中重写ConfigureModuleCatalog
方法,并通过该方法的参数IModuleCatalog
对象的非泛型函数AddModule
进行Module
类型的加载。
AddModule(IModuleInfo moduleInfo)
:加载Module
类型,需要加载的Module
类型的信息存放在参数moduleInfo中。
-
App.xaml.cs
public partial class App : PrismApplication { ...... protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { Type type = typeof(Sub.SubModule); moduleCatalog.AddModule(new ModuleInfo { ModuleName = "Hello", ModuleType = type.AssemblyQualifiedName }); } }
需要注意,创建ModuleInfo
类型时,ModuleName
必须进行初始化,否则会报错。
3、配置文件方式
配置文件的方式可以使用App.config进行配置,也可以使用xml文件的方式。
①Module配置
App.config方式:在主程序集的应用配置文件App.config(如果没有则添加)中进行module配置。
-
App.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="modules" type="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf"/> </configSections> <modules> <module assemblyFile="PrismModule.Sub" moduleType="PrismModule.Sub.SubModule, PrismModule.Sub, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ViewAModule"/> </modules> </configuration>
XML文件方式:在主程序集下新建ModuleConfig文件夹并在文件夹内创建ModuleConfig.xml进行配置,然后将该文件属性设置为资源。
-
ModuleConfig.xml
<?xml version="1.0" encoding="utf-8" ?> <m:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:m="clr-namespace:Prism.Modularity;assembly=Prism.Wpf"> <m:ModuleInfo ModuleName="ViewAModule" ModuleType="PrismModule.Sub.SubModule, PrismModule.Sub, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/> </m:ModuleCatalog>
PS:moduleType
看起来很长,但不需要死记,其实就是方式2中的ModuleType
。
②加载模块
在主程序集的App后台代码中重写CreateModuleCatalog
方法并返回一个新创的ConfigurationModuleCatalog
对象即可。
-
App.xaml.cs
public partial class App : PrismApplication { ...... protected override IModuleCatalog CreateModuleCatalog() { //App.config配置方式的module加载 return new ConfigurationModuleCatalog(); //xml文件配置方式的module加载 //return new XamlModuleCatalog(new Uri("/PrismModule.Sub;component/ModuleConfig/ModuleConfig.xml")); } }
4、扫描指定目录方式(常用)
除了上面三种加载方式外,还有一种更加解耦、更加方便的方式,就是指定Module所在目录,让程序自动去扫描并加载Module,做法很简单,只需要在主程序集的App后台代码中重写CreateModuleCatalog
方法并返回一个包含Module
存放路径信息的DirectoryModuleCatalog
对象即可。
需要注意,这种方式要在编译后将需要使用的模块程序集的dll文件放置到对应目录下,一般会在Debug目录下新建Moduls,并将编译后的需要使用的模块对应程序集dll文件放入其中
-
App.xaml.cs
public partial class App : PrismApplication { ...... protected override IModuleCatalog CreateModuleCatalog() { return new DirectoryModuleCatalog() { //表示扫描程序运行目录下的Modules文件夹,也就是Debug最里层目录下的Modules文件夹 ModulePath = ".\\Modules" }; } }
使用这种方式的好处在于,可以最大程度的让主程序集跟其他模块程序集保持解耦状态。
-
程序集编译成功后自动将dll文件放入指定目录的设置
进入程序集的属性设置,在生成后事件中输入
copy $(TargetPath) $(SolutionDir)Client\bin\Debug\net6.0-windows\Modules\$(TargetFileName) /Y
,其中Client\bin\Debug\net6.0-windows\Modules\
为解决方案下的指定目录
三、按需加载(懒加载)
Prism框架中,对于Module
的使用默认情况下是采用预加载的,但也提供了懒加载的方案,只需要在添加Module
类型的时候设置为懒加载,然后在使用module
前通过IModuleManager
对象的LoadModule(moduleName)
方法进行加载即可。
1、懒加载设置
上文学习的4种加载方式都能设置为懒加载,具体如下:
AddModule的泛型方式
AddModule<T>(string name, InitializationMode mode)
:加载模块,T
为要加载的模块类型。
-
name:主动加载模块时使用的模块名称。
-
mode:加载模式,默认为预加载模式,
InitializationMode.OnDemand
为懒加载。 -
示例
public partial class App : PrismApplication { ...... protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { moduleCatalog.AddModule<Sub.SubModule>("ViewAModule", InitializationMode.OnDemand); } }
AddModule的非泛型方式
创建ModuleInfo
时,在初始化器中将InitializationMode
属性设置为InitializationMode.OnDemand
。
-
示例
public partial class App : PrismApplication { ...... protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { moduleCatalog.AddModule(new ModuleInfo { ModuleName = "ViewAModule", ModuleType = typeof(Sub.SubModule).AssemblyQualifiedName, InitializationMode = InitializationMode.OnDemand }); } }
配置文件方式
在配置文件的module节点上,设置属性startupLoaded="false”
-
示例-App.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="modules" type="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf"/> </configSections> <modules> <module assemblyFile="PrismModule.Sub" moduleType="PrismModule.Sub.SubModule, PrismModule.Sub, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ViewAModule" startupLoaded="false"/> </modules> </configuration>
扫描目录方式
扫描目录的方式要实现懒加载需要在对应的IModule
实现类上使用特性[Module(ModuleName ="ViewAModule", OnDemand =true)]
进行标识
-
示例
[Module(ModuleName ="ViewAModule", OnDemand =true)] public class SubModule : IModule { ...... }
2、使用前进行加载
通过IOC依赖注入获得IModuleManager
对象属性,然后调用LoadModule
方法即可。
-
示例
public class MainWindowViewModel:BindableBase { [Dependency] public IRegionManager regionManager { get; set; } [Dependency] public IModuleManager moduleManager { get; set; } public ICommand BtnCommand { get => new DelegateCommand(() => { moduleManager.LoadModule("ViewAModule"); regionManager.RequestNavigate("MainRegion", "ViewA"); }); } }