Prism之Module

Prism的核心功能之一就是支持模块化应用程序开发(Modular Application Development),并且在运行时对各个模块进行动态管理。

使用Prism进行模块化开发首先要了解几个概念:

1.Module: Module是一些逻辑上相关的程序集或者资源文件的集合,在Silverlight程序中通常以xap文件为单位存在。而每一个Module中都需要有一个负责进行初始化工作以及与系统进行集成的角色,它需要实现IModule接口。IModule接口中只有一个Initialize方法,一方面这个接口将这个工程标记为一个Module,另一方面你可以在Initialize方法中实现一些逻辑,比如向容器中注册一些Service,或者将视图集成到程序中等等。

2.ModuleInfo: 在创建了一个Module之后,需要通知Prism这个Module的存在,也就是要注册一下。在Prism中,Module是以ModuleInfo的形式存在的。ModuleInfo记录了Module的信息,ModuleName属性是Module的标识符,相当于Module的ID;ModuleType是Module的AssemblyQualifiedName;DependsOn属性是该Module依赖的其它Module的ModuleName的集合,在加载该Module时,如果有依赖项没有加载的话,会先将依赖项加载;InitializationMode,有两种情况——WhenAvailable和OnDemand,当选择了WhenAvailable时,该Module会在程序启动时自动加载,如果选择了OnDemand,则会按需加载,默认情况下是WhenAvailable;Ref,存储该Module的位置,如XXX.xap;State,定义了Module从注册到加载到初始化的整个过程中的状态。

3.ModuleCatalog: ModuleCatalog实现了IModuleCatalog接口,它是ModuleInfo的容器,保存着系统中所有Module的信息,不仅会管理哪些Module需要加载,什么时候加载以什么顺序加载等问题,还要检查各个Module之间是否存在着循环依赖、是否有重复的Module等等。ModuleCatalog提供了含参构造方法和AddModule方法,可以通过代码将Module注册进去,同时也可以在xaml文件中配置好Module,然后通过ModuleCatalog.CreateFromXaml方法来加载。

4.ModuleManager: ModuleManager实现了IModuleManager接口。顾名思义就是管理Module的类。IModuleManager中含有两个方法和两个事件:Run方法会将所有InitializationMode为WhenAvailable的Module加载,然后进行初始化,初始化的工作委托给了IModuleInitializer来完成,它会获取到Module类(上面提到的实现了IModule接口的类)的实例,然后调用其Initialize方法。LoadModule方法用来加载InitializationMode为OnDemand的Module。两个事件分别用来通知下载Module的进度变化以及Module加载完成。

下面用一个示例程序来说明如何在Prism中进行模块化程序开发。

1.创建一个Silverlight Application,叫做PrismModule。

2.在Solution中添加三个Silverlight Application,分别叫做ModuleA, ModuleB, ModuleC。然后删除这三个工程中的App文件和MainPage文件。

3.在ModuleA工程下添加一个UserControl,叫做ViewA,然后再添加一个类,叫做ModuleA。并添加Microsoft.Practices.Prism和Microsoft.Practices.ServiceLocation引用。下面是ViewA和ModuleA的代码:

<UserControl x:Class="ModuleA.ViewA"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    
    <Grid x:Name="LayoutRoot" Background="White">
        <TextBlock Text="Module A" FontSize="22" />
    </Grid>
</UserControl>
public class ModuleA : IModule
{
    public void Initialize()
    {
    }
}

4.对ModuleB和ModuleC重复做步骤3的操作,只是将文本改成相应模块。

5.在PrismModule中添加对ModuleA、ModuleB、ModuleC、Prism、UnityExtensions还有Unity for Silverlight的引用,然后创建Shell和Bootstrapper。添加一个UserControl,叫做Shell;再添加一个类,叫做Bootstrapper。

Shell代码如下:

<UserControl x:Class="PrismModule.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:prism="http://www.codeplex.com/prism"
    mc:Ignorable="d"
    d:DesignHeight="600" d:DesignWidth="800">
    
    <StackPanel Margin="50">
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <Border VerticalAlignment="Top" BorderBrush="Red" BorderThickness="2" Width="200" Height="100">
                <ContentControl prism:RegionManager.RegionName="RegionA" />
            </Border>
            <Border VerticalAlignment="Top" BorderBrush="Red" BorderThickness="2" Width="200" Height="100">
                <ContentControl prism:RegionManager.RegionName="RegionB" />
            </Border>
            <StackPanel>
                <Border BorderBrush="Red" BorderThickness="2" Width="200" Height="100">
                    <ContentControl prism:RegionManager.RegionName="RegionC" />
                </Border>
                <Button Content="Load Module C" Click="LoadModuleC" Width="120" Height="25" />
            </StackPanel>
        </StackPanel>
    </StackPanel>
</UserControl>

Shell.xaml.cs代码如下:

public partial class Shell : UserControl
{
    private IModuleManager _moduleManager;

    public Shell(IModuleManager moduleManager)
    {
        InitializeComponent();
        _moduleManager = moduleManager;
    }

    private void LoadModuleC(object sender, RoutedEventArgs e)
    {
        //  因为ModuleC的InitializationMode是OnDemand,
        //  所以要手动请求
        _moduleManager.LoadModule("ModuleC");
    }
}

Bootstrapper代码如下:

public class Bootstrapper : UnityBootstrapper
{
    protected override DependencyObject CreateShell()
    {
        return this.Container.TryResolve<Shell>();
    }

    protected override void InitializeShell()
    {
        App.Current.RootVisual = (UIElement)this.Shell;
    }

    protected override IModuleCatalog CreateModuleCatalog()
    {
        return new ModuleCatalog();
    }

    protected override void ConfigureModuleCatalog()
    {
        Type typeA = typeof(ModuleA.ModuleA);
        ModuleInfo moduleA = new ModuleInfo
        {   //  ModuleA没有设置InitializationMode,默认为WhenAvailable
            ModuleName = typeA.Name,
            ModuleType = typeA.AssemblyQualifiedName,
        };

        Type typeB = typeof(ModuleB.ModuleB);
        ModuleInfo moduleB = new ModuleInfo
        {
            ModuleName = typeB.Name,
            ModuleType = typeB.AssemblyQualifiedName,
            InitializationMode = InitializationMode.OnDemand,
        };

        Type typeC = typeof(ModuleC.ModuleC);
        ModuleInfo moduleC = new ModuleInfo
        {
            ModuleName = typeC.Name,
            ModuleType = typeC.AssemblyQualifiedName,
            InitializationMode = InitializationMode.OnDemand,
            //  ModuleC依赖于ModuleB
            DependsOn = new Collection<string> { moduleB.ModuleName },
        };

        this.ModuleCatalog.AddModule(moduleA);
        this.ModuleCatalog.AddModule(moduleB);
        this.ModuleCatalog.AddModule(moduleC);
    }
}

将App.xaml.cs中的Application_Startup方法改为

private void Application_Startup(object sender, StartupEventArgs e)
{
    Bootstrapper bootstrapper = new Bootstrapper();
    bootstrapper.Run();
}
6.现在已经有了Region,需要将各个Module中的View填充到Region中。修改ModuleA,ModuleB和ModuleC的Initialize方法。
public void Initialize()
{
    ServiceLocator.Current.GetInstance<IRegionManager>().
        RegisterViewWithRegion("RegionA", typeof(ViewA));
}

将其中的A改为相应的字母。运行程序,结果如下:

Prism之Module_第1张图片

我们点击按钮来加载ModuleC,因为ModuleC依赖于ModuleB,所以ModuleB也一块儿加载出来了。但是这与我们预期的效果不太一致。因为一共只load了一个xap文件,用WinRAR打开看一下,发现三个Module的程序集都在其中。

Prism之Module_第2张图片

在Silverlight程序中,模块化程序开发应该不仅仅体现在开发时的模块化,运行时也应该是模块化的。比如ModuleA在程序加载时就load出来,但是ModuleB和ModuleC则是在点击了按钮后才load出来的,换句话说,在没点按钮前就不应该将ModuleB和ModuleC的程序集加载进来。现在由于PrismModule项目引用了三个Module,所以程序集会被一块打包进xap文件中。我们修改一下,将对ModuleB和ModuleC的引用的Copy Local属性设置为false:

Prism之Module_第3张图片Prism之Module_第4张图片

重新编译一下,再次查看xap文件,发现已经没有了ModuleB和ModuleC。

Prism之Module_第5张图片

运行程序,报错。很简单,因为我们在Bootstrapper中用到了ModuleB和ModuleC,缺少了这两个dll,程序没法运行。为了解决这个问题,我们把初始化ModuleCatalog的过程改一下,不使用代码,而是使用配置文件。在Silverlight中,Prism支持使用xaml文件作为配置文件。下面在PrismModule工程下新建一个资源文件,ModuleCatalog.xaml。内容如下:

<Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"       
        xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism">
   
    <Modularity:ModuleInfo Ref="ModuleA.xap" ModuleName="ModuleA" 
        ModuleType="ModuleA.ModuleA, ModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />

    <Modularity:ModuleInfo Ref="ModuleB.xap" ModuleName="ModuleB" InitializationMode="OnDemand"
        ModuleType="ModuleB.ModuleB, ModuleB, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />

    <Modularity:ModuleInfo Ref="ModuleC.xap" ModuleName="ModuleC" InitializationMode="OnDemand"
        ModuleType="ModuleC.ModuleC, ModuleC, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
        <Modularity:ModuleInfo.DependsOn>
            <sys:String>ModuleB</sys:String>
        </Modularity:ModuleInfo.DependsOn>
    </Modularity:ModuleInfo>
</Modularity:ModuleCatalog>

这里大体和用代码写一致,只不过Ref属性里要写明该Module对应的是哪个xap包。Prism在Silverlight程序中使用一个叫做XapModuleTypeLoader的类来加载Module,在将Module下载之后会获取AppManifest.xaml文件,也就是说如果你的Module是个类库工程的话,会在加载时产生错误。可以将几个类库的程序集文件包装在一个xap文件中作为一个Module来使用,或者自定义一个ModuleTypeLoader。

定义完Module的配置文件后,要改写Bootstrapper。首先删除用代码配置Module的方法ConfigureModuleCatalog,然后在CreateModuleCatalog方法中替换成一下内容:

protected override IModuleCatalog CreateModuleCatalog()
{
    return Microsoft.Practices.Prism.Modularity.ModuleCatalog.CreateFromXaml(
        new Uri("/PrismModule;component/ModuleCatalog.xaml", UriKind.Relative));
}

再次运行程序,正常运行。

Prism之Module_第6张图片

这样就达到了按需加载的目的。节约带宽是一个好处,如果产品是分模块往外卖的时候,可以由客户按需定制。

不过再打开ModuleB和ModuleC的xap文件看一下,发现里面不仅有Module本身的程序集,还包括了引用的Prism的程序集等。而这些程序集其实已经在PrismModule.xap中包含了。完全没有必要重复下载。所以可以将多余的程序集的引用的Copy Local属性设置为false,这样就瘦身成功了。(想要避免重复加载相同的文件,也可以通过在项目的Properties面板中勾选Reduce XAP size by using application library caching选项)

如果你对Module的加载到执行的整个过程感兴趣,那么Prism本身提供了一个QuickStart,既有Unity版本也有Mef版本,不要错过。

 

代码下载

你可能感兴趣的:(Module)