模块化管理-Model
Module
是功能和资源的逻辑集合,其打包方式可以单独开发、测试、部署并集成到应用程序中。
当随着项目较为庞大,有大量的不同业务、View
、ViewModel
时,就需要考虑根据不同业务进行程序集的拆分了,而Module
就是帮助我们管理不同的程序集并集成到应用程序中。
每个模块化管理的程序集中都有一个模块核心类,IModule
的子类。主程序集通过加载这个IModule
的子类,就可以将对应程序集集成到主程序集的prism框架中来。
一、简单使用
这里以主程序使用其他模块管理的程序集中的Region
视图为例进行Module
的简单使用。
首先在主程序集所在的解决方案中添加新的WPF用户控件库、创建Views与ViewModels文件夹、引入prism.Unity
库。
①、View层
在Views中添加将要使用用户控件ViewA
<UserControl ......>
<Grid>
<StackPanel>
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</Grid>
</UserControl>
②、ViewModel层
在ViewModels中创建对应的视图模型ViewAViewModel
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时,不会因此而抛出异常。而且思路跟整体性会较好。
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
类型。
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中。
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配置。
<?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进行配置,然后将该文件属性设置为资源。
<?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
对象即可。
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文件放入其中。
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”
<?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");
});
}
}