C# + WPF + MEFを使ってアプリにプラグインを実装してみる その2

# はじめに
[C# + WPF + MEFを使ってアプリにプラグインを実装してみる その1 ](http://qiita.com/Shiranui_Isuzu/items/84ddd6613da6c825a524)の続きです。
今回は、UserControlをプラグインとして分離したいと思います。

GitHub : https://github.com/isuzu-shiranui/CalculationApplication2

# 読み込み側
## View (MainWindow)
### 外観
![24e811368140649951cdb5df777a1164.png](https://qiita-image-store.s3.amazonaws.com/0/133834/89dbdfed-7738-4f56-ef93-264bec1793a0.png)
今回はUserControlだけ置いてあるため、真っ白。

### コード
Views/MainWindow.xaml

```xml
<Window x:Class="CalculationApplication2.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CalculationApplication2.Views"
        xmlns:vm="clr-namespace:CalculationApplication2.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="326" Width="591">

    <Window.DataContext>
        <vm:MainWindowViewModel />
    </Window.DataContext>

    <Grid>
        <UserControl Content="{Binding Plugin}" />
    </Grid>
</Window>
```

Views/MainWindow.xaml.cs

```csharp
using System.Windows;

namespace CalculationApplication2.Views
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}
```

## ViewModel (MainWindowViewModel)
ViewModels/MainWindowViewModel

```csharp
using Plugin;
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;

namespace CalculationApplication2.ViewModels
{
    public class MainWindowViewModel
    {
        [Import(typeof(IPlugin))]
        public IPlugin Plugin { get; set; }

        public MainWindowViewModel()
        {
            LoadPlugins();
        }

        /// <summary>
        /// プラグインを全て読みこんで、[Import]がついているプロパティに格納する。
        /// </summary>
        private void LoadPlugins()
        {
            //フォルダがなければ作る。
            string pluginsPath = Directory.GetCurrentDirectory() + @"\plugins";
            if (!Directory.Exists(pluginsPath)) Directory.CreateDirectory(pluginsPath);

            //プラグイン読み込み
            using (var catalog = new DirectoryCatalog(pluginsPath, "SummationPlugin.dll"))
            using (var container = new CompositionContainer(catalog))
            {
                if (catalog.LoadedFiles.Count > 0) container.SatisfyImportsOnce(this);
            }
        }
    }
}
```

##App.xaml

 ```StartupUri="MainWindow.xaml"``` →  ```StartupUri="Views/MainWindow.xaml"```に変更

# プラグイン用インターフェース作成
## IPlugin

```csharp
namespace Plugin
{
    public interface IPlugin
    {
    }
}
```

# UserControlプラグイン作成
## View (SummationView)
### 外観
![dd2c473fbe2f6e0ec2fff02f5f0c5b49.png](https://qiita-image-store.s3.amazonaws.com/0/133834/fc18cae7-2108-161a-ccaf-24ce459f94a9.png)
前回の読み込み側のViewをそのままUserControlに移しただけです(；・∀・)

### コード
Views/SummationView.xaml

```xml
<UserControl x:Class="SummationPlugin.Views.SummationView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:vm="clr-namespace:SummationPlugin.ViewModels"
             xmlns:local="clr-namespace:SummationPlugin"
             mc:Ignorable="d" 
             d:DesignHeight="300" Width="522">

    <UserControl.DataContext>
        <vm:SummationViewModel />
    </UserControl.DataContext>
    
    <Grid Background="White">
        <StackPanel HorizontalAlignment="Left" Height="24" Margin="10,113,0,0" VerticalAlignment="Top" Width="499" Orientation="Horizontal">
            <Label Content="左辺" />
            <TextBox Width="212" Text="{Binding LeftValue, Mode=TwoWay}"/>
            <Label Content="左辺" />
            <TextBox Width="212" Text="{Binding RightValue, Mode=TwoWay}"/>
        </StackPanel>
        <Button Content="実行" HorizontalAlignment="Left" Margin="425,239,0,0" VerticalAlignment="Top" Width="75" Command="{Binding CalculationCommand, UpdateSourceTrigger=PropertyChanged}"/>
        <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="44,172,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="211" Text="{Binding ResultValue}"/>
        <Label x:Name="label" Content="結果" HorizontalAlignment="Left" Margin="10,172,0,0" VerticalAlignment="Top"/>
    </Grid>
</UserControl>
```

Views/SummationView.xaml.cs

```csharp
using Plugin;
using System.ComponentModel.Composition;
using System.Windows.Controls;

namespace SummationPlugin.Views
{
    /// <summary>
    /// SummationView.xaml の相互作用ロジック
    /// </summary>
    [Export(typeof(IPlugin))]
    public partial class SummationView : UserControl, IPlugin
    {
        public SummationView()
        {
            InitializeComponent();
        }
    }
}
```

## ViewModel (SummationViewModel)
ViewModels/SummationViewModel.cs

```csharp
using Prism.Commands;
using System.ComponentModel.DataAnnotations;

namespace SummationPlugin.ViewModels
{
    class SummationViewModel : Commons.ViewModelBase
    {
        private double leftValue;
        [Required]
        [Range(0, double.MaxValue)]
        public double LeftValue
        {
            get { return leftValue; }
            set { SetProperty(ref leftValue, value); CalculationCommand.RaiseCanExecuteChanged(); }
        }

        private double rightValue;
        [Required]
        [Range(0, double.MaxValue)]
        public double RightValue
        {
            get { return rightValue; }
            set { SetProperty(ref rightValue, value); CalculationCommand.RaiseCanExecuteChanged(); }
        }

        private double resultValue;
        public double ResultValue
        {
            get { return resultValue; }
            set { SetProperty(ref resultValue, value); }
        }

        private DelegateCommand calculationCommand;
        public DelegateCommand CalculationCommand => calculationCommand ?? (new DelegateCommand(CalculationExecute, () => !HasErrors));

        private void CalculationExecute()
        {
            ResultValue = LeftValue + RightValue;
        }
    }
}
```

## ViewModelBase
前回と一緒...のはず

```csharp
using Prism.Mvvm;
using System;
using System.Runtime.CompilerServices;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace SummationPlugin.Commons
{
    public class ViewModelBase : BindableBase, INotifyDataErrorInfo
    {
        private ErrorsContainer<string> errorsContainer;

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public ViewModelBase()
        {
            errorsContainer = new ErrorsContainer<string>(propertyName => ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)));
        }

        public bool HasErrors => errorsContainer.HasErrors;

        public IEnumerable GetErrors(string propertyName)
        {
            return errorsContainer.GetErrors(propertyName);
        }

        protected override bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if(!base.SetProperty(ref storage, value, propertyName)) return false;

            var context = new ValidationContext(this) { MemberName = propertyName };
            var errors = new List<ValidationResult>();
            if(!Validator.TryValidateProperty(value, context, errors))
            {
                errorsContainer.SetErrors(propertyName, errors.Select(error => error.ErrorMessage).ToArray());
            }
            else
            {
                errorsContainer.ClearErrors(propertyName);
            }
            return true;
        }
    }
}
```

# 動かしてみる
## プラグインを配置する
まずは作ったプラグインをビルドします。
すると、プラグインのDebugまたはReleaseフォルダに、(プラグイン名).dllができていると思います。
![0ea13532f170ded53995de29c71ad362.png](https://qiita-image-store.s3.amazonaws.com/0/133834/77f89c71-3c58-aecb-dce4-642c60111587.png)

これを、読み込みアプリのDebugまたはReleaseフォルダにpluginフォルダがなければ作って貼り付けます。
今回は、プラグイン側にもPrismを使っているので、Prism.dllとPrism.Wpf.dllも追加します。
![c7bce116c51320987f29c99ae98c689c.png](https://qiita-image-store.s3.amazonaws.com/0/133834/25ef5ac9-7ae7-131a-54c1-63df89e9e43f.png)

## 読み込み側アプリの動作確認
実際にアプリを起動して、計算が行われれば完成です。見た目上は前回と同じ動きになります。
![45ba1eefcb45d4df5e0c684388cf4b84.gif](https://qiita-image-store.s3.amazonaws.com/0/133834/809c22e6-6cd7-0185-e16b-6423b60fd751.gif)

# さいごに
見てもらって分かったと思いますが、基本的には前回と変わりません。

次は複数プラグインへの対応をやっていこうかなと思ってます。

冒頭にも乗っけてますが、GitHubにありますので試してみてください。
GitHub : https://github.com/isuzu-shiranui/CalculationApplication2