Silverlight2では、通常1つのアプリケーションが1つの*****.xapというzip形式で圧縮されたファイルとしてサーバ上に配備されます。
まだ、そこまで大きなアプリケーションを組んだことはないですが、本格的なアプリケーションを作るとxapが、ものすごいサイズになってしまって初回のダウンロードがとても大変になってしまいそうです。
そのため、業務単位とか適当な単位でSilverlight Applicationを分割してといった作りになりそうですが、それはそれでアプリケーション間での連携とかがちょっとめんどくさそうな気がしてます。
(よく調べないで言ってるので、いい方法もあるのかもしれません。知ってたらコメントください!!)
そんな時に、お手軽に使えそうないいものがComposite Application Guidance for WPF and Silverlight Feb2009の中にありました。
モジュールや関連するアセンブリをShellのあるxapとは別のxapに定義して、動的にそれを読み込ませることが割りと簡単に出来るっぽい。ということでHello world的に試してみました。
プロジェクトの作成
まず、プロジェクトを作成します。
作成するプロジェクトは以下の4つです。
- DynamicLoadApplication(Silverlightアプリケーション)
- DynamicLoadApplication.Web(1を作ったときに作られる)
- ModuleA(Silverlightクラスライブラリ)
- ModuleB(Silverlightアプリケーション):<テスト用ページは作成しない>
SilverlightのプロジェクトにはComposite Application Guidanceのライブラリを参照に追加しておきます。
ついでに、DynamicLoadApplicationでShellとBootstrapperを作成してApp.xaml.csでBootstrapperを起動するコードを書いておきます。
ここら辺はいつもどおりなので割愛。
Shellの作成
Shellを作っていきます。
といってもItemsControlを置いてRegionを定義するだけです(^^;
<UserControl x:Class="DynamicLoadApplication.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Regions="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly=Microsoft.Practices.Composite.Presentation">
<Grid x:Name="LayoutRoot" Background="White">
<ItemsControl
Regions:RegionManager.RegionName="MainRegion" />
</Grid>
</UserControl>
ModuleAの作成
次に、通常のクラスライブラリで作ってModuleAを作成します。
ModuleAは、ボタンをクリックしたタイミングでModuleBを読み込むようにします。そのため、IModuleManagerをDIしています。
後は、普通のModuleと同じです。
ModuleAView.xaml
<UserControl x:Class="ModuleA.ModuleAView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Border BorderBrush="Blue" BorderThickness="1" Background="White">
<StackPanel Margin="5">
<TextBlock Text="ModuleA" />
<Button Content="Load ModuleB"
Click="ModuleBLoad" />
</StackPanel>
</Border>
</UserControl>
ModuleAView.xaml.cs
using System.Windows;
using System.Windows.Controls;
using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Unity;
namespace ModuleA
{
public partial class ModuleAView : UserControl
{
// モジュール読み込みに必要なモジュールマネージャを
// Unityからセットしてもらう。
[Dependency]
public IModuleManager ModuleManager { get; set; }
public ModuleAView()
{
InitializeComponent();
}
private void ModuleBLoad(object sender, RoutedEventArgs e)
{
// ModuleBの読み込み
this.ModuleManager.LoadModule("ModuleB");
}
}
}
Viewが出来たので、モジュールクラスを作成します。
モジュールクラスでは、MainRegionにModuleAViewを追加するだけです。
ModuleA.cs
using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Composite.Regions;
using Microsoft.Practices.Unity;
namespace ModuleA
{
public class ModuleA : IModule
{
[Dependency]
public IUnityContainer Container { get; set; }
[Dependency]
public IRegionManager RegionManager { get; set; }
public void Initialize()
{
RegionManager.RegisterViewWithRegion("MainRegion",
() => Container.Resolve<ModuleAView>());
}
}
}
ModuleAはこれで完成なので、DynamicLoadApplicationの参照にModuleAを追加して、Bootstrapperに以下GetModuleCatalogを以下のようにします。
protected override IModuleCatalog GetModuleCatalog()
{
return new ModuleCatalog()
// ModuleAを追加
.AddModule(typeof(ModuleA.ModuleA));
}
この状態で実行すると、下のような画面が表示されます。
この時点でLoad ModuleBボタンを押しても、まだModuleBが無いのでModuleNotFoundExceptionが発生してしまいます。
ということで、今日の本題である別のxapにあるModuleを動的に読み込むということをしてみようと思います。
ModuleBの作成
ということで、ModuleBを作成していきます。まずは、見た目から作ります。
ModuleBViewという名前でユーザコントロールを作成して、以下のような見た目にします。
ModuleBView.xaml
<UserControl x:Class="ModuleB.ModuleBView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Border Background="White" BorderBrush="Red" BorderThickness="1">
<TextBlock Text="ModuleB" Margin="5" />
</Border>
</UserControl>
そして、モジュールクラスを作成します。
こちらも、ModuleBViewをMainRegionに追加するだけのシンプルなものです。
ModuleB.cs
using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Composite.Regions;
using Microsoft.Practices.Unity;
namespace ModuleB
{
public class ModuleB : IModule
{
[Dependency]
public IUnityContainer Container { get; set; }
[Dependency]
public IRegionManager RegionManager { get; set; }
public void Initialize()
{
RegionManager.RegisterViewWithRegion("MainRegion",
() => Container.Resolve<ModuleBView>());
}
}
}
これでModuleB自体は完成です。
ModuleAと違うのは、DynamicLoadApplicationにModuleBの参照は追加しないということです。
ModuleB.xapから動的に色々読み込むようにModuleCatalogに追加する処理を追加します。
protected override IModuleCatalog GetModuleCatalog()
{
return new ModuleCatalog()
// ModuleAを追加
.AddModule(typeof(ModuleA.ModuleA))
// ModuleBを追加
.AddModule(
"ModuleB",
"ModuleB.ModuleB, ModuleB, Version=1.0.0.0",
"ModuleB.xap",
InitializationMode.OnDemand);
}
最後に追加されたAddModuleでは、「ModuleB」という名前(ここは任意に決めれる)で、「ModuleB.ModuleB, ModuleB, Version1.0.0.0」という名前のクラスをモジュールとして読み込みます。
DLLとかはModuleB.xapにまとまって入っています。後は必要になったらDLしてきてね。
という感じのことが指定されています。
ということで実行してみます。
実行直後
Load ModuleBを押すとModuleBが読み込まれる
複数のXAPに分けて開発が出来て、それを割りと自然にくっつけることが出来るのはComposite Application Guidance for WPF and Silverlight Feb2009だけ!
(WebClientとか使ってごにょごにょやれば自作でも問題なく出来ます)