04Prism WPF 入门实战 - Module

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了04Prism WPF 入门实战 - Module相关的知识,希望对你有一定的参考价值。

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.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的时候就已经自动创建好了。

[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实现

<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,添加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实现

<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("联系人王某");
    }
}

以上是关于04Prism WPF 入门实战 - Module的主要内容,如果未能解决你的问题,请参考以下文章

01Prism WPF 入门实战 - 项目准备

03Prism WPF 入门实战 - Region

05Prism WPF 入门实战 - Navigation

06Prism WPF 入门实战 - Log&控件库

Prism入门之模块(Module)

从PRISM开始学WPFPrism-Module?