.NETで作る!

.NETに関するあれこれ(C#、VB.NET)

Unity+app.configでIDbConnectionにインスタンスを注入する

前回の続き。こちらがもともとやりたかったこと。

前準備

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" />

IDbConnectionSqlConnectionエイリアスを作っておきます。前回の記事では、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に置き換えできますね!うーん、素晴らしい。

. .