【WPF Prism】 ViewModelLocatorがViewにViewModelを紐づけるまで

## はじめに

[前回記事](https://qiita.com/t13801206/items/588daff87ccb74ec7538)でPrismのDIを試した

その際、インターフェイスを登録し忘れて実態クラスを`ViewModel`の引数に渡していた

その時の様子は以下

```cs
public class MainWindowViewModel : BindableBase
{
    IPerson person;

    public MainWindowViewModel(Person person)
    {
        this.person = person;
    }
    // ViewModelのコンストラクターはこれだけ。引数なしのコンストラクターは存在しない。
}
```

てっきり**DIコンテナーにインターフェイスと実装を登録しないと動かない、エラーが出る**ものかと重いっていたが、正常に画面が立ち上がった

どういう経緯なのか調べる

## ViewModelLocator

`View`と`ViewModel`を紐づけるのが`ViewModelLocator`の役割
[ViewModelLocator - Prism Library](https://prismlibrary.com/docs/viewmodel-locator.html)

`prism:ViewModelLocator.AutoWireViewModel="True"`を`Window`もしくは`UserControl`タグ内に書くことで、その機能が有効になる

（参考）：[公式HPを訳しているQiita記事](https://qiita.com/azumabashi/items/4ecfc4df97a85ba411c7)

### ViewModelLocatorをつかわない場合

比較のために、Prismの機能をつかわない場合を載せる

#### 方法1: XAMLでViewModelを指定する

```xml:MainWindowView.xaml
<Window.DataContext>
    <vm:MainWindowViewModel />
</Window.DataContext>
```

ただし、今回のようにコンストラクターに引数が必要な場合（`ViewModel(Person person)`）はエラーがでるため使えない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/f5251817-5979-e169-5125-97e274535d31.png)

（引数なしのコンストラクターを別途用意すれば使えなくはないが・・）

#### 方法2: コードビハインドでViewModelを指定する

引数がある場合は、コードビハインドで指定する

```cs
public MainWindow()
{
    InitializeComponent();

    this.DataContext = new MainWindoViewModels(new Person()); // ←
}
```

## 【本題】ViewModelLocatorがViewModelをバインドするまで

Prsimの`ViewModelLocator.AutoWireViewModel`について調べる

調べるといっても、GitHubからコードをおとして関数をたどっていくだけの作業

https://github.com/PrismLibrary/Prism

### [ViewModelLocatorクラス](https://github.com/PrismLibrary/Prism/blob/master/src/Wpf/Prism.Wpf/Mvvm/ViewModelLocator.cs)


```cs
// 1.XAML添付プロパティ prism:ViewModelLocator.AutoWireViewModel
DependencyProperty AutoWireViewModelProperty = DependencyProperty.RegisterAttached(
    "AutoWireViewModel",        // 登録する依存関係プロパティの名前
    typeof(bool?),              // プロパティの型
    typeof(ViewModelLocator),   // 依存関係プロパティを登録する所有者型
    new PropertyMetadata(
        defaultValue: null,     // 依存関係プロパティの既定値
        // プロパティの有効値が変更されるときにプロパティ システムによって必ず呼び出されるハンドラー
        propertyChangedCallback: AutoWireViewModelChanged)
        );

// 2. 添付プロパティが変更されたら呼ばれるハンドラー
AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    // EventArgs e から添付プロパティ値を取得 -> True なら、プロバイダのメソッドを呼ぶ
    // d: プロパティの値が変更されたビュー
    // Bind: view.DataContext = viewModel
    ViewModelLocationProvider.AutoWireViewModelChanged(d, Bind);
    // 呼び出し元と名前が同じでややこしい
}

// 3. プロバイダ側で呼ばれるコールバック関数
// DataContextをビューにセットする
Bind(object view, object viewModel)
```

（参考）：[WPF4.5入門 その45 「添付プロパティ」 - かずきのBlog@hatena](https://blog.okazuki.jp/entry/2014/08/20/081810)

### [ViewModelLocationProviderクラス](https://github.com/PrismLibrary/Prism/blob/master/src/Prism.Core/Mvvm/ViewModelLocationProvider.cs)

ビューの`AutoWireViewModel`プロパティが変更されたらメソッドが呼ばれる

[ViewModelLocatorのルール(convention)](https://prismlibrary.com/docs/viewmodel-locator.html)にしたがって、`***View`と`***ViewModel`のマッピングがなされる

```cs
// AutoWireViewModelが変更されたら呼ばれる
// 引数1: プロパティが変更されたビュー(Dependency Object)
// 引数2: Bind(view, viewmodel)コールバック関数
void AutoWireViewModelChanged(object view, Action<object, object> setDataContextCallback)
{
    // 登録されているビューモデルがあれば、それを使う
    object viewModel = GetViewModelForView(view);

    // 登録されたビューモデルがみつからなかったら、ルールに従ってビューモデルを探す
    if (viewModel == null)
    {
        // （略）

        viewModel = _defaultViewModelFactoryWithViewParameter != null ? 
        _defaultViewModelFactoryWithViewParameter(view, viewModelType) 
        : _defaultViewModelFactory(viewModelType);
    }

    setDataContextCallback(view, viewModel); // view.DataContext = viewModel
}
```

Factory|実装|結果
-|-|-
`_defaultViewModelFactoryWithViewParameter(view, viewModelType) `| `Prism.Ioc.ContainerLocator.Container.Resolve(viewModelType)`|OK
`_defaultViewModelFactory(viewModelType)`|`System.Activator.CreateInstance(viewModelType)`|System.MissingMethodException: 'No parameterless constructor defined for type 'FullApp.ViewModels.MainWindowViewModel'.'

パラメーター付きの`ViewModel`だとエラーがでるのは、XAMLで指定する場合と似ている

### `View`のコードビハインド

コードビハインドで同じことをやる

```cs
public MainWindow()
{
    InitializeComponent();

    var viewModelType = typeof(ViewModels.MainWindowViewModel);
    var viewModel = Prism.Ioc.ContainerLocator.Container.Resolve(viewModelType);
    
    this.DataContext = viewModel;
}
```

### [UnityContainer.Resolve()](https://github.com/unitycontainer/abstractions/blob/master/src/Abstracts/IUnityContainer.cs)

次に調べるべきは`IUnityContainer.Resolve()`

もう力が尽きたので、インターフェイスのサマリーだけ読んだ

> Unityは型が登録されているかどうかをチェックし、登録されていれば登録情報を使用してオブジェクトを作成する。
> 型が登録されていない場合、リフレクションを使用して型に関する情報を取得し、リフレクションデータを使用して型をインスタンス化および初期化するためのパイプラインを作成する。

`MainWindowViewModel`は登録されていないので、リフレクションによって再帰的にインスタンスが生成されたと推測される

## おわりに

Prismの`ViewModelLocator`がどのように`ViewModel`のインスタンスを生成するか追っていった

最後は**UnityContainerライブラリの`Resolve`メソッドの実装によっている**
・・というところまでたどって力尽きた

## （おまけ）UnityContainerの開発が終了していた

https://twitter.com/okazuki/status/1481031462535430147?s=20&t=AsMoS6qI6PVII-7XTsiAHA

コミュニティがこのプロジェクトに対する興味を完全に失ったので、このプロジェクトはもうメンテナンスしません。今後リリース予定もありません。

> Due to a complete lack of interest from the community to support this project, it has been archived and will no longer be maintained. There will be no more releases of this library.

https://github.com/unitycontainer

http://unitycontainer.org/