この記事は
Prism Libarary のModule
機能と、DIコンテナ(Prism.Unity
)を併用する場合に
少し工夫(というほどでもないけど)が必要になったので、それを書き残すための記事
環境: .NET Core 3.1, Prism.Unity 8.0
具体的には
以下2つのやりたいことと解決するための方法を書く
1つ目
やりたいこと:MainWindowViewModel
において、DIコンテナからモジュールのクラスを取得・利用したい
解決方法:MainWindowViewModel
のコンストラクタ内で、明示的にIModuleManager.LoadModule(使いたいモジュール)
を呼び出す
2つ目
やりたいこと:あるモジュールにおいて、他のモジュールのクラスを参照したクラスをDI登録したい
解決方法:IModule
を継承したクラス内で、IContainerRegistry.GetContainer()
したコンテナを使ってDI登録する。その際、メインプロジェクトのDependsOn(依存先)でモジュールを読み込む順番を明示する。
さっきから言っているモジュールとは
↓ 公式ドキュメントのモジュールに関するページ
検証用プロジェクトの構成
本題に入る前に、検証用ソリューションの説明 (PlantUML
を使ってみたかっただけ)
-
Prism.Modularity.IModule
インターフェイスを実装したModule1
~Module3
がある - メインプロジェクト(
PrismModuleTest
)で、すべてのモジュールをmoduleCatalog.AddModule
している - メインプロジェクトの
MainWindowViewModel
で、Module1
がDI登録したクラスを使いたい- ↑ これがやりたいことの1つ目
-
Module1
ではModule3
のクラスを使ったクラスをDI登録したい- ↑ これがやりたいことの2つ目
-
Module2
は比較のために置いてあるだけで、とくに使うことはない
いろいろ試してみたけど、図をきれいに整えることはできなかった・・・
MainWindowViewModel
でDIコンテナからモジュールのクラスを取得・利用したい
まず、やりたことの1つ目について書く
本家GitHub↓の07-Modules - Code
を参考にした
つまずいたところ
メインプロジェクトで使ってもらうクラスをモジュール側で用意し、DI登録して
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<IModuleClass, Module1Class>(nameof(Module1Class)); // DI登録
}
メインプロジェクトApp.xaml.cs
でConfigureModuleCatalog
メソッドを用いて
各モジュールを指定して (MainWindowViewModel
でモジュール1を使う予定)
public partial class App
{
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<Module1.Module1Module>(); // <- これを使う
moduleCatalog.AddModule<Module2.Module2Module>();
moduleCatalog.AddModule<Module3.Module3Module>();
}
}
MainWindowViewModel
のコンストラクターの中で
メンバ変数である_module1Class
にDIコンテナからインスタンスを代入しようとしたところ
public MainWindowViewModel(IContainerProvider container)
{
_module1Class = container.Resolve<IModuleClass>("Module1Class");
}
// エラーメッセージ
// Prism.Ioc.ContainerResolutionException:
// 'An unexpected error occurred while resolving 'InterfaceLibrary.IModuleClass', with the service name 'Module1Class''
「Resolve
できませんよ~」的なエラーが出た
ログを確認すると
App
クラスのそれぞれメソッドが実行されたあと、すぐにMainWindowViewModel
のコンストラクタが実行されている
つまり、DIコンテナにModule1
がクラスを登録する前にResolve
を試みてエラーになっていた
App # RegisterTypes
App # ConfigurationModuleCatalog
App # CreateShell
MainWindowViewModel # コンストラクター [開始]
例外がスローされました: 'Prism.Ioc.ContainerResolutionException' (Prism.Unity.Wpf.dll の中)
解決方法
MainWindowViewModel
のコンストラクタでcontainer.Resolve()
をする前に、モジュール側のDI登録を済ませればよい
DI登録を促すために、コンストラクタ内でLoadModule
を呼ぶ
そうすればModule1
側のRegisterTypes
が実行され、DI登録が完了した状態でMainWidowViewModel
からResolve
できる
-public MainWindowViewModel(IContainerProvider container)
+public MainWindowViewModel(IContainerProvider container, IModuleManager moduleManager)
{
+ moduleManager.LoadModule(nameof(Module1.Module1Module)); // モジュールをマニュアルロードをしてから
_m odule1Class = container.Resolve<IModuleClass>("Module1Class"); // Resolve する
}
うまくいった場合のログ
MainWindowViewModel
のコンストラクタの中でModule1
がロードされている
-
New !!
はDI登録時ではなく、Resolve
によりインスタンスが生成されたタイミングに出力されている - モジュール2, 3 は登録のみで
Resolve
されずインスタンスが生成されていないので、New !!
のログがない
App # RegisterTypes
App # ConfigurationModuleCatalog
App # CreateShell
MainWindowViewModel # コンストラクター [開始]
Module1Module # RegisterTypes
Module1Module # OnInitialized
_moduleManager_LoadModuleCompleted <-- Module1Module
Module1Class New !! // <-- MainWindowViewModelのメンバ変数に代入されたタイミング
MainWindowViewModel # コンストラクター [終了]
Module2Module # RegisterTypes
Module2Module # OnInitialized
_moduleManager_LoadModuleCompleted <-- Module2Module
Module3Module # RegisterTypes
Module3Module # OnInitialized
_moduleManager_LoadModuleCompleted <-- Module3Module
参考) モジュール1~3が有するクラス
public class Module1Class : IModuleClass
{
public Module1Class()
{
Debug.WriteLine($"{nameof(Module1Class)} New !!");
} // コンストラクタが呼ばれたら New !! を出力する
}
// Module2, 3 も共通
イベントを登録しておくことで、いつモジュールがロードされたのかを監視している (↓参考)
余談
上記のようにマニュアル操作でモジュールをロードすることを明示したい場合は
App
クラスのほうも変更する
-moduleCatalog.AddModule<Module1.Module1Module>();
+moduleCatalog.AddModule<Module1.Module1Module>(InitializationMode.OnDemand);
// オンデマンドを引数に与える
今回はメインプロジェクトのウィンドウでモジュールを使うのでオンデマンド指定はしなかった
別ケースとして、後々にしか使わないモジュールについては
オンデマンドを指定することで、アプリの起動速度向上に寄与するかもしれない(?)
あるモジュールから他のモジュールのクラスを参照し、そのクラスをDI登録したい
つづいて、やりたいことの2つ目について書く
つまづいたことろの前に、小さくつまづいたこところ
モジュール内で、DIコンテナから何かインスタンスを引っ張り出して、それを使って登録したい
しかし、モジュール内の動きとしてはRegisterTypes
(登録)が先に動き、そのあとOnInitialized
(containerProvider:解決系)が動くため、「解決(DI Resolve)してから登録が」できない
Module1Module # RegisterTypes
Module1Module # OnInitialized
これについては、正攻法ではないかもしれないが、登録用のcontainerRegistry
をメンバ変数に格納しておいて
あとからOnInitialize
でメンバ変数を使って登録作業をするとした
public class Module1Module : IModule
{
+ IUnityContainer _container;
public void RegisterTypes(IContainerRegistry containerRegistry)
{
+ _container = containerRegistry.GetContainer();
containerRegistry.Register<IModuleClass, Module1Class>(nameof(Module1Class));
}
}
つまずいたところ本題
モジュール3のクラスをつかって、モジュール1側でなにかインスタンスをDI登録する
public class Module1Module : IModule
{
IUnityContainer _container;
public void OnInitialized(IContainerProvider containerProvider)
{
+ // モジュール3のクラスのプロパティを使って
+ var module3property = containerProvider.Resolve<IModuleClass>("Module3Class").ModuleNumber;
+ // 何かのインスタンスをDI登録したい
+ _container.RegisterInstance<ISomeClass>("someclass", new SomeClass(module3property));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
_container = containerRegistry.GetContainer(); // 登録するためのコンテナ
containerRegistry.Register<IModuleClass, Module1Class>(nameof(Module1Class));
}
}
実行したら、先ほどと同じようなエラーが出た
Prism.Ioc.ContainerResolutionException: 'An unexpected error occurred while resolving 'InterfaceLibrary.IModuleClass', with the service name 'Module3Class''
「(モジュール1で)モジュール3を使いたいようだけど、モジュール3クラスの名前解決ができないよ」と。
解決方法
App.xaml.cs
でAddModule
するときに、モジュール同士の依存関係を明示する
モジュール1はモジュール3を使うので、dependsOn: nameof(Module3.Module3Module)
とする
public partial class App
{
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
- moduleCatalog.AddModule<Module1.Module1Module>();
+ moduleCatalog.AddModule<Module1.Module1Module>(dependsOn: nameof(Module3.Module3Module))
moduleCatalog.AddModule<Module2.Module2Module>();
moduleCatalog.AddModule<Module3.Module3Module>();
}
}
うまくいった場合のログ
- モジュール3が先にロードされ、DI登録される。この時点ではインスタンスは生成されていない。
- モジュール1がロードされ、モジュール3のクラスを使ったときにインスタンスが生成されている (New !!)
- モジュール1の New !! は
MainWindowViewModel
のメンバ変数に代入されたときに表示される - モジュール2は諸々が終わってからひっそりロードされる
App # RegisterTypes
App # ConfigurationModuleCatalog
App # CreateShell
MainWindowViewModel # コンストラクター [開始]
Module3Module # RegisterTypes // モジュール1がマニュアルロードされたので、それに先立ちモジュール3がロードされ始めた
Module3Module # OnInitialized
_moduleManager_LoadModuleCompleted <-- Module3Module
Module1Module # RegisterTypes
Module1Module # OnInitialized
Module3Class New !! // モジュール1で呼ばれた
_moduleManager_LoadModuleCompleted <-- Module1Module
Module1Class New !! // MainWindowViewModelで呼ばれた
MainWindowViewModel # コンストラクター [終了]
Module2Module # RegisterTypes
Module2Module # OnInitialized
_moduleManager_LoadModuleCompleted <-- Module2Module
おわりに
ポイントは2つ
- せっかちに使いたいモジュールは、使いたいタイミングでマニュアルロードすべし
- モジュール同士に依存関係がある場合は、カタログ登録時にメインプロジェクトに関係性をお知らせすべし