1.概要
源码及PPT地址:https://github.com/JusterZhu/wemail
视频地址:https://www.bilibili.com/video/BV1KQ4y1C7tg?sharesource=copyweb
Module,具有特定功能,且独立存在则称为成为模块。下图为Prism体系中的关系结构图。
在Prism体系中Module的应用分为
注册/发现模块
加载模块
初始化模块
2.详细内容
(1)注册/发现模块
通过重写CreateModuleCatalog方法指定加载module的方式,这里我个人比较推荐使用反射的方式去指定目录下读取,当然还有其他方式这里就不一 一介绍了。
首先我们将项目中的module编译生成到项目运行目录下的Apps文件夹下。
这时需要在类库右键->点击属性。
将DLL编译生成时拷贝到,指定目录下(详情见源码)。
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App
{/// <summary>/// 应用程序启动时创建Shell/// </summary>/// <returns></returns>protected override Window CreateShell(){return Container.Resolve<MainWindow>();}protected override void RegisterTypes(IContainerRegistry containerRegistry){//注册服务、依赖、View}/// <summary>/// 配置区域适配/// </summary>/// <param name="regionAdapterMappings"></param>protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings){base.ConfigureRegionAdapterMappings(regionAdapterMappings);}protected override IModuleCatalog CreateModuleCatalog(){//new ConfigurationModuleCatalog()//指定模块加载方式为从文件夹中以反射发现并加载module(推荐用法)return new DirectoryModuleCatalog() { ModulePath = @".\Apps" };}
}
(2)加载模块
加载模块的代码实现在DirectoryModuleCatalog内部实现大致如下:
//
// Summary:
// Represets a catalog created from a directory on disk.
//
// Remarks:
// The directory catalog will scan the contents of a directory, locating classes
// that implement Prism.Modularity.IModule and add them to the catalog based on
// contents in their associated Prism.Modularity.ModuleAttribute. Assemblies are
// loaded into a new application domain with ReflectionOnlyLoad. The application
// domain is destroyed once the assemblies have been discovered. The diretory catalog
// does not continue to monitor the directory after it has created the initialze
// catalog.
public class DirectoryModuleCatalog : ModuleCatalog
{private class InnerModuleInfoLoader : MarshalByRefObject{internal ModuleInfo[] GetModuleInfos(string path){DirectoryInfo directory = new DirectoryInfo(path);ResolveEventHandler value = (object sender, ResolveEventArgs args) => OnReflectionOnlyResolve(args, directory);AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += value;ModuleInfo[] result = GetNotAlreadyLoadedModuleInfos(IModuleType: AppDomain.CurrentDomain.GetAssemblies().First((Assembly asm) => asm.FullName == typeof(IModule).Assembly.FullName).GetType(typeof(IModule).FullName), directory: directory).ToArray();AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= value;return result;}private static IEnumerable<ModuleInfo> GetNotAlreadyLoadedModuleInfos(DirectoryInfo directory, Type IModuleType){List<Assembly> list = new List<Assembly>();Assembly[] alreadyLoadedAssemblies = (from p in AppDomain.CurrentDomain.GetAssemblies()where !p.IsDynamicselect p).ToArray();foreach (FileInfo item in (from file in directory.GetFiles("*.dll")where alreadyLoadedAssemblies.FirstOrDefault((Assembly assembly) => string.Compare(Path.GetFileName(assembly.Location), file.Name, StringComparison.OrdinalIgnoreCase) == 0) == nullselect file).ToList()){try{list.Add(Assembly.LoadFrom(item.FullName));}catch (BadImageFormatException){}}return list.SelectMany((Assembly assembly) => from t in assembly.GetExportedTypes().Where(new Func<Type, bool>(IModuleType.IsAssignableFrom))where t != IModuleTypewhere !t.IsAbstractselect t into typeselect CreateModuleInfo(type));}private static Assembly OnReflectionOnlyResolve(ResolveEventArgs args, DirectoryInfo directory){Assembly assembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault((Assembly asm) => string.Equals(asm.FullName, args.Name, StringComparison.OrdinalIgnoreCase));if (assembly != null){return assembly;}AssemblyName assemblyName = new AssemblyName(args.Name);string text = Path.Combine(directory.FullName, assemblyName.Name + ".dll");if (File.Exists(text)){return Assembly.ReflectionOnlyLoadFrom(text);}return Assembly.ReflectionOnlyLoad(args.Name);}internal void LoadAssemblies(IEnumerable<string> assemblies){foreach (string assembly in assemblies){try{Assembly.ReflectionOnlyLoadFrom(assembly);}catch (FileNotFoundException){}}}private static ModuleInfo CreateModuleInfo(Type type){string name = type.Name;List<string> list = new List<string>();bool flag = false;CustomAttributeData customAttributeData = CustomAttributeData.GetCustomAttributes(type).FirstOrDefault((CustomAttributeData cad) => cad.Constructor.DeclaringType!.FullName == typeof(ModuleAttribute).FullName);if (customAttributeData != null){foreach (CustomAttributeNamedArgument namedArgument in customAttributeData.NamedArguments){switch (namedArgument.MemberInfo.Name){case "ModuleName":name = (string)namedArgument.TypedValue.Value;break;case "OnDemand":flag = (bool)namedArgument.TypedValue.Value;break;case "StartupLoaded":flag = !(bool)namedArgument.TypedValue.Value;break;}}}foreach (CustomAttributeData item in from cad in CustomAttributeData.GetCustomAttributes(type)where cad.Constructor.DeclaringType!.FullName == typeof(ModuleDependencyAttribute).FullNameselect cad){list.Add((string)item.ConstructorArguments[0].Value);}ModuleInfo obj = new ModuleInfo(name, type.AssemblyQualifiedName){InitializationMode = (flag ? InitializationMode.OnDemand : InitializationMode.WhenAvailable),Ref = type.Assembly.EscapedCodeBase};obj.DependsOn.AddRange(list);return obj;}}//// Summary:// Directory containing modules to search for.public string ModulePath{get;set;}//// Summary:// Drives the main logic of building the child domain and searching for the assemblies.protected override void InnerLoad(){if (string.IsNullOrEmpty(ModulePath)){throw new InvalidOperationException(Prism.Properties.Resources.ModulePathCannotBeNullOrEmpty);}if (!Directory.Exists(ModulePath)){throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Prism.Properties.Resources.DirectoryNotFound, ModulePath));}AppDomain currentDomain = AppDomain.CurrentDomain;try{List<string> list = new List<string>();IEnumerable<string> collection = from Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()where !(assembly is AssemblyBuilder) && assembly.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder" && !string.IsNullOrEmpty(assembly.Location)select assembly.Location;list.AddRange(collection);Type typeFromHandle = typeof(InnerModuleInfoLoader);if (typeFromHandle.Assembly != null){InnerModuleInfoLoader innerModuleInfoLoader = (InnerModuleInfoLoader)currentDomain.CreateInstanceFrom(typeFromHandle.Assembly.Location, typeFromHandle.FullName)!.Unwrap();base.Items.AddRange(innerModuleInfoLoader.GetModuleInfos(ModulePath));}}catch (Exception innerException){throw new Exception("There was an error loading assemblies.", innerException);}}
}
(3)初始化模块
这些代码在使用Prism项目模板创建Module的时候就已经自动创建好了。
[Module(ModuleName = "Contact")]
public class ContactModule : IModule
{//初始化public void OnInitialized(IContainerProvider containerProvider){//通过注册RegionManager,添加ContactViewvar regionManager = containerProvider.Resolve<IRegionManager>();//通过ContentRegion管理导航默认初始页面ContactViewvar contentRegion = regionManager.Regions["ContentRegion"];contentRegion.RequestNavigate(nameof(ContactView));}public void RegisterTypes(IContainerRegistry containerRegistry){containerRegistry.RegisterForNavigation<ContactView>();}
}
3.实战应用
Shell - MainWindow实现
<Window.Resources><Style x:Key="ModuleItemSytle" TargetType="ListBoxItem"><Setter Property="Template"><Setter.Value><ControlTemplate><Grid><TextBlock Text="{Binding ModuleName}"></TextBlock></Grid></ControlTemplate></Setter.Value></Setter></Style>
</Window.Resources>
<Grid><Grid.ColumnDefinitions><ColumnDefinition Width="2*"/><ColumnDefinition Width="8*"/></Grid.ColumnDefinitions><ListBox ItemsSource="{Binding Modules}" SelectedItem="{Binding ModuleInfo}" ItemContainerStyle="{StaticResource ModuleItemSytle}" /><ContentControl Grid.Column="1" prism:RegionManager.RegionName="ContentRegion"/>
</Grid>
</Window>
MainWindowViewModel中的实现
public class MainWindowViewModel : BindableBase
{private string _title = "Prism Application";//Region管理对象private IRegionManager _regionManager;private IModuleCatalog _moduleCatalog;private ObservableCollection<IModuleInfo> _modules;private DelegateCommand _loadModules;private IModuleInfo _moduleInfo;public IView View { get; set; }public string Title{get { return _title; }set { SetProperty(ref _title, value); }}public ObservableCollection<IModuleInfo> Modules{get => _modules ?? (_modules = new ObservableCollection<IModuleInfo>());}public DelegateCommand LoadModules { get => _loadModules = new DelegateCommand(InitModules); }public IModuleInfo ModuleInfo { get {return _moduleInfo; }set {_moduleInfo = value;Navigate(value);}}public MainWindowViewModel(IRegionManager regionManager, IModuleCatalog moduleCatalog){_regionManager = regionManager;_moduleCatalog = moduleCatalog;}public void InitModules() {var dirModuleCatalog = _moduleCatalog as DirectoryModuleCatalog;Modules.AddRange(dirModuleCatalog.Modules);}private void Navigate(IModuleInfo info) {_regionManager.RequestNavigate("ContentRegion", $"{ info.ModuleName }View");}
}
Module - Wemail.Contact实现
ContactModule.cs 模块标记文件
[Module(ModuleName = "Contact")]
public class ContactModule : IModule
{public void OnInitialized(IContainerProvider containerProvider){//通过注册RegionManager,添加ContactViewvar regionManager = containerProvider.Resolve<IRegionManager>();//通过ContentRegion管理导航默认初始页面ContactViewvar contentRegion = regionManager.Regions["ContentRegion"];contentRegion.RequestNavigate(nameof(ContactView));}public void RegisterTypes(IContainerRegistry containerRegistry){containerRegistry.RegisterForNavigation<ContactView>();}
}
ContactView实现
<Grid><Grid.ColumnDefinitions><ColumnDefinition Width="2*"/><ColumnDefinition Width="8*"/></Grid.ColumnDefinitions><ListBox x:Name="LsbContact" ItemsSource="{Binding Contacts}"/><ContentControl HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="40" Grid.Column="1" Content="{Binding ElementName=LsbContact,Path=SelectedItem}"></ContentControl>
</Grid>
ContactViewModel实现
public class ContactViewModel : BindableBase
{private ObservableCollection<string> _contacts;private string _message;public string Message{get { return _message; }set { SetProperty(ref _message, value); }}public ObservableCollection<string> Contacts { get => _contacts ?? (_contacts = new ObservableCollection<string>()); }public ContactViewModel(){Message = "Wemail.Contact Prism Module";Contacts.Add("联系人张某");Contacts.Add("联系人王某");}
}