新しいデスクトップ アプリケーションを開始しており、MVVM と WPF を使用して構築したいと考えています。
私もTDDを使用するつもりです。
問題は、IoC コンテナーを使用して本番コードに依存関係を注入する方法がわからないことです。
次のクラスとインターフェースがあるとします。
public interface IStorage
{
bool SaveFile(string content);
}
public class Storage : IStorage
{
public bool SaveFile(string content){
// Saves the file using StreamWriter
}
}
そして、依存関係として別のクラスがありIStorage
、このクラスも ViewModel またはビジネス クラスであると仮定します...
public class SomeViewModel
{
private IStorage _storage;
public SomeViewModel(IStorage storage){
_storage = storage;
}
}
これにより、モックなどを使用して、ユニット テストを簡単に記述し、適切に動作しているかどうかを確認できます。
問題は、実際のアプリケーションで使用する場合です。インターフェイスのデフォルト実装をリンクする IoC コンテナーが必要なのはわかっていますIStorage
が、どうすればよいでしょうか?
たとえば、次の xaml があったらどうなるでしょうか。
<Window
... xmlns definitions ...
>
<Window.DataContext>
<local:SomeViewModel />
</Window.DataContext>
</Window>
その場合、WPF に依存関係を注入するように正しく指示するにはどうすればよいでしょうか?
SomeViewModel
また、 C# コードからのインスタンスが必要な場合、どうすればよいでしょうか?
私はこの件で完全に迷っているように感じています。これを処理するための最善の方法について、例やガイダンスがあれば教えていただければ幸いです。
私は StructureMap に精通していますが、専門家ではありません。また、より優れた/簡単な/すぐに使えるフレームワークがあれば、教えてください。
ベストアンサー1
私は Ninject を使っていますが、使い心地がよいと感じています。すべてがコードで設定されており、構文も非常にわかりやすく、ドキュメントも充実しています (SO にはたくさんの回答があります)。
基本的には次のようになります:
ビュー モデルを作成し、IStorage
インターフェイスをコンストラクター パラメーターとして取得します。
class UserControlViewModel
{
public UserControlViewModel(IStorage storage)
{
}
}
ViewModelLocator
ビュー モデルの get プロパティを持つを作成し、Ninject からビュー モデルを読み込みます。
class ViewModelLocator
{
public UserControlViewModel UserControlViewModel
{
get { return IocKernel.Get<UserControlViewModel>();} // Loading UserControlViewModel will automatically load the binding for IStorage
}
}
ViewModelLocator
App.xaml でアプリケーション全体のリソースを作成します。
<Application ...>
<Application.Resources>
<local:ViewModelLocator x:Key="ViewModelLocator"/>
</Application.Resources>
</Application>
DataContext
の をUserControl
ViewModelLocator 内の対応するプロパティにバインドします。
<UserControl ...
DataContext="{Binding UserControlViewModel, Source={StaticResource ViewModelLocator}}">
<Grid>
</Grid>
</UserControl>
NinjectModule を継承するクラスを作成します。これにより、必要なバインディング (IStorage
およびビューモデル) が設定されます。
class IocConfiguration : NinjectModule
{
public override void Load()
{
Bind<IStorage>().To<Storage>().InSingletonScope(); // Reuse same storage every time
Bind<UserControlViewModel>().ToSelf().InTransientScope(); // Create new instance every time
}
}
アプリケーションの起動時に、必要な Ninject モジュール (現時点では上記のモジュール) を使用して IoC カーネルを初期化します。
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
IocKernel.Initialize(new IocConfiguration());
base.OnStartup(e);
}
}
IoC カーネルのアプリケーション全体のインスタンスを保持するために静的クラスを使用したのでIocKernel
、必要なときに簡単にアクセスできます。
public static class IocKernel
{
private static StandardKernel _kernel;
public static T Get<T>()
{
return _kernel.Get<T>();
}
public static void Initialize(params INinjectModule[] modules)
{
if (_kernel == null)
{
_kernel = new StandardKernel(modules);
}
}
}
このソリューションでは、クラスの依存関係を隠すため、一般的にアンチパターンと見なされる静的ServiceLocator
( IocKernel
) を使用します。ただし、UI クラスにはパラメータなしのコンストラクターが必要であり、インスタンス化を制御できないため、VM を挿入できないため、UI クラスに対する何らかの手動サービス検索を回避することは非常に困難です。少なくともこの方法を使用すると、すべてのビジネス ロジックが存在する VM を分離してテストできます。
もっと良い方法があれば、ぜひ共有してください。
編集:Lucky Likeyは、NinjectにUIクラスをインスタンス化させることで静的サービスロケータを取り除くための回答を提供しました。回答の詳細は以下で確認できます。ここ