Unity Application Block 1.1 Released

Enterprise Library 4.0がリリースされて、これに搭載されているDIコンテナのObjectBuilderが「Unity Application Block」と名前を改められてリリースされた。

Enterprise Library自体は(いろんな意味で)重たすぎて普段は使っていないけど、ObjectBuilderにはちょっとだけ興味があった。

上記の記事にもあるようにObjectBuilderの特徴は属性ベースのインジェクションとインジェクションする過程(プロセス)を細かく制御できるということ。

DIコンテナと言えばSpring.NETをがっつり使っているので、いまさら他のライブラリに乗り換える理由も必要もないんだけど、だからといって無視するわけにはいかないのでちょろっと触ってみた。

単純なインジェクション

まずはインジェクションするクラスを定義する。

Hoge.cs
using System;
using System.Diagnostics;

using Microsoft.Practices.Unity;

namespace UnityTest {
   public class Hoge {
       /// <summary>
       /// Messageを取得、設定します。
       /// </summary>
       public string Message {
           get;
           set;
       }
   }
}

そして、これをインジェクションする対象のクラス。わかりやすいようにフォームにした。

Form1.cs
using System;
using System.Windows.Forms;

using Microsoft.Practices.Unity;

namespace UnityTest {
   public partial class Form1 : Form {
       /// <summary>
       /// Hogeを取得、設定します。
       /// </summary>
       [Dependency]
       public Hoge Hoge {
           get;
           set;
       }

       public Form1() {
           InitializeComponent();
       }

       private void button1_Click(object sender, EventArgs e) {
           MessageBox.Show(this, Hoge.Message);
       }
   }
}

Hoge型で「Hoge」というプロパティを定義して、Dependencyというカスタム属性でマークしている。

最後はコンテナからオブジェクトを取り出すコード。

Program.cs
using System;
using System.Configuration;
using System.Windows.Forms;

using Microsoft.Practices.Unity;

namespace UnityTest {
   static class Program {
       /// <summary>
       /// アプリケーションのメイン エントリ ポイントです。
       /// </summary>
       [STAThread]
       static void Main() {
           var dicon = new UnityContainer();

           Application.Run(dicon.Resolve<Form1>());
       }
   }
}

UnityContainerクラスをインスタンス化して、Resolveメソッドで型引数にForm1を渡して呼び出す。

これだけでForm1クラスをインスタンス化してHogeプロパティにHogeクラスのインスタンスをインジェクションした状態で取得することができる。

Dependency属性でマークしたプロパティの型に従って自動的にインジェクションする型を判断するわけだけど、これだとHogeの状態が一つしか持てないので、例えばMessageプロパティの値が「Hello, Hoge」と「Hello, Fuge」とそれぞれ違うHogeインスタンスをインジェクションできない事になってしまう。

そんな時はDependency属性の引数にオブジェクト名を指定すればいい。

Form1.cs
/// <summary>
/// Hoge1を取得、設定します。
/// </summary>
[Dependency("hoge1")]
public Hoge Hoge1 {
  get;
  set;
}

/// <summary>
/// Hoge2を取得、設定します。
/// </summary>
[Dependency("hoge2")]
public Hoge Hoge2 {
   get;
   set;
}

こんな感じでプロパティを二つ用意して、それぞれのDependency属性で「hoge1」「hoge2」とオブジェクト名を指定する。

あとはコンテナを取得してから、RegisterInstanceメソッドを使って以下のようにそれぞれのインスタンスを登録する。

Program.cs
var dicon = new UnityContainer();
dicon.RegisterInstance(typeof(Hoge), "hoge1", new Hoge {
   Message = "Hello, Hoge"
});
dicon.RegisterInstance(typeof(Hoge), "hoge2", new Hoge {
   Message = "Hello, Fuge"
});

これで同じ型の複数のインスタンスをインジェクションできるようになる。この設定は属性ではできないのでコードでやる必要がある。ちなみにこれらの設定は全てアプリケーション構成ファイルに記述することもできる(通常はそちらを使うことになると思う)。

AppSettingsの設定をインジェクションする

DIコンテナを使うメリットの一つとして、アプリケーション構成ファイルや独自ファイルの設定を自動的にインジェクションできるというのがある。

Unityにそういった機能があるか調べてみたけど、見つけること事ができなかった(あったら教えてください)。

なので独自に作ってみる事にした。

まずアプリケーション構成ファイルの設定。「hoge.message」というキー名で値を宣言している。

App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>

   <appSettings>
       <add key="hoge.message" value="Hello, World" />
   </appSettings>
    
</configuration>

そして、これをHogeクラスのMessageプロパティにインジェクションする設定。

Hoge.cs
/// <summary>
/// Messageを取得、設定します。
/// </summary>
[Dependency("hoge.message")]
public string Message {
   get;
   set;
}

もちろんこれで実行したら「hoge.message」なんていうオブジェクトはコンテナに存在しないので例外が出る。

じゃあどうすればいいかと言えばコンテナを取得してからAppSettingsの内容を全部RegisterInstanceメソッドで登録してしまえばいいんだけど、毎回そんなコードは書いていられないので、コンテナの挙動を拡張するという手段を取る。

コンテナの挙動を拡張するには「〜Extension」(こういったクラスが何種類かある)というクラスを使う。

ここでは「UnityContainerExtension」というクラスを使う(他にも色々なExtensionクラスが用意されているけど現段階ではどう使い分けるか解らない)。

AppSettingsInjector.cs
using System;
using System.Configuration;
using System.Windows.Forms;

using Microsoft.Practices.Unity;

namespace UnityTest {
   class AppSettingsInjector : UnityContainerExtension {

       protected override void Initialize() {
           var appSettings = ConfigurationManager.AppSettings;

           foreach(var key in appSettings.AllKeys) {
               this.Container.RegisterInstance(typeof(string), key, appSettings[key]);
           }
       }
   }
}

Initializeメソッドをオーバーライドして、その中でAppSettingsの内容をRegisterInstanceメソッドで追加している。

そして、このクラスをコンテナを取得した後にAddNewExtensionメソッドで追加すればいい。

Program.cs
var dicon = new UnityContainer();
dicon.AddNewExtension<AppSettingsInjector>();

これでAppSettingsの内容をオブジェクトにインジェクションする事ができるようになる*1

まだちょっとしか触っていないので何とも言えないけど、カスタマイズしだいではSpring.NET並に使えるようになるかもしれない。逆に言えばカスタマイズしないと使えない。

*1:といっても型コンバータとかがないのでstring型にしかインジェクションできないけどね