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编译生成时拷贝到,指定目录下(详情见源码)。
代码语言:javascript复制/// <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内部实现大致如下:
代码语言:javascript复制//
// 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.IsDynamic
select 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) == null
select 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 != IModuleType
where !t.IsAbstract
select t into type
select 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).FullName
select 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的时候就已经自动创建好了。
代码语言:javascript复制[Module(ModuleName = "Contact")]
public class ContactModule : IModule
{
//初始化
public void OnInitialized(IContainerProvider containerProvider)
{
//通过注册RegionManager,添加ContactView
var regionManager = containerProvider.Resolve<IRegionManager>();
//通过ContentRegion管理导航默认初始页面ContactView
var contentRegion = regionManager.Regions["ContentRegion"];
contentRegion.RequestNavigate(nameof(ContactView));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ContactView>();
}
}
3.实战应用
Shell - MainWindow实现
代码语言:javascript复制<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中的实现
代码语言:javascript复制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 模块标记文件
代码语言:javascript复制[Module(ModuleName = "Contact")]
public class ContactModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
//通过注册RegionManager,添加ContactView
var regionManager = containerProvider.Resolve<IRegionManager>();
//通过ContentRegion管理导航默认初始页面ContactView
var contentRegion = regionManager.Regions["ContentRegion"];
contentRegion.RequestNavigate(nameof(ContactView));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ContactView>();
}
}
ContactView实现
代码语言:javascript复制<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实现
代码语言:javascript复制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("联系人王某");
}
}