前回の続き。こちらがもともとやりたかったこと。
前準備
NuGetから「Unity」をインストールしておいてください。
Main.vb
Imports Microsoft.Practices.Unity.Configuration Imports Microsoft.Practices.Unity Imports Microsoft.Practices.ServiceLocation Module Module1 Sub Main() '注意! 'LoadConfigurationは拡張メソッドなので、 'Imports Microsoft.Practices.Unity.Configuration 'をしないと利用できません。 Dim container As New UnityContainer() '設定ファイルで指定したコンテナを使用する container.LoadConfiguration(My.Settings.UseContainer) Dim service As New UnityServiceLocator(container) ServiceLocator.SetLocatorProvider(Function() service) Dim cn = ServiceLocator.Current.GetInstance(Of IDbConnection)() Console.WriteLine(cn.ConnectionString) Console.WriteLine("何かキーを押してください") Console.ReadKey() End Sub End Module
前回のコードとほぼ同じ。今回はIDbConnection
という頻出インターフェイスを実装したクラスを取得しています。接続文字列とかそういうものは一切記述していませんが、DBへ接続が可能です。その理由は、もちろんapp.configにあります。
container.LoadConfiguration(My.Settings.UseContainer)
のコードの意味の説明は後回し…
app.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/> <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" > <section name="UnityConfigDatabase.My.MySettings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" /> </sectionGroup> </configSections> <!--MySettings--> <userSettings> <UnityConfigDatabase.My.MySettings> <!--使用するコンテナを記述--> <setting name="UseContainer" serializeAs="String" > <value>develop</value> </setting> </UnityConfigDatabase.My.MySettings> </userSettings> <!--unity--> <unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <alias alias="IDbConnection" type="System.Data.IDbConnection, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <alias alias="SqlConnection" type="System.Data.SqlClient.SqlConnection, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <!--モック--> <container name="mock"> <register type="IDbConnection" mapTo="UnityConfigDatabase.MockConnection, UnityConfigDatabase" /> </container> <!--開発用環境セット--> <container name="develop"> <register type="IDbConnection" mapTo="SqlConnection"> <constructor> <param name="connectionString" value="Data Source=.\SQLEXPRESS;Initial Catalog=AdventureWorks_Develop;Integrated Security=SSPI" /> </constructor> </register> </container> <!--製品用環境セット--> <container name="product"> <register type="IDbConnection" mapTo="SqlConnection"> <constructor> <param name="connectionString" value="Data Source=.\SQLEXPRESS;Initial Catalog=AdventureWorks;Integrated Security=SSPI" /> </constructor> </register> </container> </unity> </configuration>
前回よりちょっとコードが長いですがやってることは変わりません。重要なポイントを抜き出して説明します。
alias セクション
<alias alias="IDbConnection" type="System.Data.IDbConnection, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <alias alias="SqlConnection" type="System.Data.SqlClient.SqlConnection, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
IDbConnection
、SqlConnection
のエイリアスを作っておきます。前回の記事では、typeの記述にバージョン番号記述していませんでしたが、今回は明記しています。理由は「省略したらエラーが出た」から。細かいことはわかりませんが、エラーがでたら明示してみましょう。
container セクション
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <!--モック--> <container name="mock"> … </container> <!--開発用環境セット--> <container name="develop"> … </container> <!--製品用環境セット--> <container name="product"> … </container> </unity> </unity>
今回はコンテナを切り替えしたいので、name
属性を使用して「mock(モック)」、「develop(開発用環境セット)」、「product(製品用環境セット)」を付けています。
このように名前を付けることで、VB側のコードで利用するコンテナを指定して読み込むことができます。
Dim container As New UnityContainer() container.LoadConfiguration("develop")
これでも悪くないのですが、せっかく抽象化したのにVB側のソースを触らないと設定を切り替えできないのはイマイチ。そこでアプリケーション設定を使って、これもapp.configで指定できるようにしてしまいましょう。
<!--使用するコンテナを記述--> <setting name="UseContainer" serializeAs="String" > <value>develop</value> </setting>
Dim container As New UnityContainer() container.LoadConfiguration(My.Settings.UseContainer)
完璧ですね!
constructor セクション
コンストラクタに引数を渡します。
<register type="IDbConnection" mapTo="SqlConnection"> <constructor> <param name="connectionString" value="Data Source=.\SQLEXPRESS;Initial Catalog=AdventureWorks_Develop;Integrated Security=SSPI" /> </constructor> </register>
コンストラクタの引数が不要なクラスの場合、constructor
セクションは不要です。
ちなみに今回の場合、constructor
セクションを省略するとエラーが出ます。Dim c = IDbConnection = New SqlConnection
と書いてもコンパイルは通るのになぜでしょう?
ドキュメントには
Specifies the parameters of the constructor to call. If no child elements are present, it indicates that the zero-argument constructor should be called. This element is optional.
The Unity Configuration Schema
とあるので、省略すると「zero-argument constructor should be called」なはずなんですけどね。引数がある場合は引数を全部指定した方がいいのかもしれません。
まとめ
今回のサンプルでは、ソースコードを一切変更せずに、開発環境、本番環境の切り替えができるようになりました。
「接続文字列だけ」の切り替え例ではありますが、IDbConnection
さえ実装しているなら別にSqlConnection
じゃなくてもいいというのがUnityというかIoCの良いところ。詳しくは説明しませんが、
<!--モック--> <container name="mock"> <register type="IDbConnection" mapTo="UnityConfigDatabase.MockConnection, UnityConfigDatabase" /> </container>
コレです。もう何でも指定できます。MockConnectionのソースコードは本当にモックなので省略。それでも見たい方はソースコードDLしてください。
おわり
以前に書いた記事は完全にUnityに置き換えできますね!うーん、素晴らしい。