1. ホーム
  2. c#

[解決済み] WPF/MVVMアプリケーションで依存性注入を処理する方法

2022-09-02 07:47:22

質問

新しいデスクトップアプリケーションを始めるのですが、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 インターフェイスのデフォルトの実装をリンクする IoC コンテナを用意しなければならないことは分かっていますが、どのようにすればよいのでしょうか?

例えば、以下のようなxamlがあった場合、どうでしょうか。

<Window 
    ... xmlns definitions ...
>
   <Window.DataContext>
        <local:SomeViewModel />
   </Window.DataContext>
</Window>

この場合、どのようにすればWPFに依存性を注入するように正しく「伝える」ことができますか?

また、もし私が SomeViewModel のインスタンスが必要な場合、どのようにすればよいでしょうか?

私はこれで完全に失われた感じです、私はそれを処理するための最良の方法である任意の例または指導をお願いします。

StructureMapには詳しいのですが、専門家ではありません。また、より良い/より簡単な/すぐに使えるフレームワークがあれば、教えてください。

どのように解決するのですか?

私はNinjectを使用していて、それを使って仕事をするのは楽しいことだとわかりました。すべてはコードで設定され、構文はかなり単純で、良いドキュメントがあります(そしてSOに多くの回答があります)。

ですから、基本的にはこのようになります。

ビューモデルを作成し IStorage インターフェースをコンストラクタの引数として与えます。

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

を作成します。 ViewModelLocator を作成し、Ninjectからビューモデルを読み込むgetプロパティを持たせる。

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>

をバインドする。 DataContextUserControl を 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
    }
}

アプリケーション起動時にIoCカーネルを必要なNinjectモジュール(とりあえずは上のもの)で初期化する。

public partial class App : Application
{       
    protected override void OnStartup(StartupEventArgs e)
    {
        IocKernel.Initialize(new IocConfiguration());

        base.OnStartup(e);
    }
}

私は静的な IocKernel クラスを使用して、IoC カーネルのアプリケーション全体のインスタンスを保持し、必要なときに簡単にアクセスできるようにしています。

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をインジェクトできないので、ある種の手動サービスルックアップを避けることは非常に困難である。少なくともこの方法では、すべてのビジネス ロジックがある VM を分離してテストすることができます。

もし誰かがより良い方法を知っているならば、ぜひ教えてください。

EDIT Lucky Likey が、Ninject が UI クラスをインスタンス化することで、静的サービスロケータを取り除く方法を提供してくれました。その詳細は以下の通りです。 をご覧ください。