DebugとReleaseでApp.configを切り替える(その2:XDT 編)

あれ、震災後まだ一回も更新してなかったですかね(読んでる人はわずかでしょうけど) 

以前の記事で propertyGroup のなかに appConfig タグを入れて設定ファイルを切り替える方法を書きました。このやりかたはシンプルですが、AppConfig をまるごと差し替えるので、ファイルの内容が多くなると両方に同じ記述を転記したりしなければならないなどメンテが面倒な感じになります。

実はWebアプリであれば、面白い仕組みがあります。Web.configというファイルとは別に、Web.debug.config と Web.release.config という元のファイルを変換するxmlを定義することで、ビルド後の配置するときに Web.confg を適切なものに差し替えるという仕組みですね。この変換するためのしくみが XML Document Transform (XDT) です。ただ、Visual Studio 2010のばあい、デフォルトでは、Webアプリのビルドの時にしか作動していないようです。

これを、AppConfig でもできるようにするやり方を書いている記事を見つけました。Vishal Joshi's Tangent: Applying XDT magic to App.Configです。大変親切に書いてあるので、このままやれば出来ますが、例によって英語で読むのが面倒だという人のために概略を紹介します(すいませんが、C#のプロジェクトを前提にしてます)

1. ベースとなる app.config の作成

WPFやWinFormプロジェクトを作成し、 app.config を次のように記述します。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="hoge" value="aaaa" />    
  </appSettings>
</configuration>
2. 変換する app.debug.config の作成

プロジェクトに app.debug.config を追加し、次のように記述します。

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add key="hoge" value="bbbb" xdt:Transform="Replace" xdt:Locator="Match(key)"/>
  </appSettings>
</configuration>

この記述は app.config から hoge というキーをもつ add 要素を探して(xdt:Locator="Match(key)")、value属性を bbbb でおきかえよ(xdt:Transform="Replace")という意味ですね。

3. プロジェクトファイルの Include の変更

プロジェクトファイル(*.csproj)を編集します(プロジェクトを「アンロード」すると、プロジェクトのコンテキストメニューに「編集 hogehoge.csproj 」というメニューが出るので、VS上で編集できます。しばらくこれに気がつかず、VS環境外からテキストエディタを使ってました……)

configの記述を探します。

<None Include="app.config" />
<None Include="app.debug.config" />

これを

<Content Include="app.config" />
<Content Include="app.debug.config" >
    <DependentUpon>app.config</DependentUpon>
</Content>

こう書き換えます。なお、NoneをContentに書き換えることについて、原文には「This is a tiny pre-requisite for WPP but if you encounter any issues because of this then we can dig the work around…」(これはWPF向けに必須ですが、これが原因で問題が起きた場合は他に何らかの回避方法があると思います)とありましたので、WinFormだとNoneのままでもいいのかもしれません。僕はWPFプロジェクトだったので書き換えてやっています。問題は出ていません。

4. ProjectConfigFileName の追加

プロジェクトファイル(*.csproj)のPropertyGroup の配下に ProjectConfigFileName 要素を追加します。

<PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
    <ProductVersion>8.0.30703</ProductVersion>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>{2D587604-866B-4675-8587-FA9728EC59D8}</ProjectGuid>
    <ProjectConfigFileName>App.Config</ProjectConfigFileName>
5. Import の追加

プロジェクトファイル(*.csproj)の

  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

というタグの下に

  <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets" />
  <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />

を追加します(二行目は、引用元の記事の本文には追加指示がありませんが、コメント欄で指摘されています。二行目がないとビルドエラーになります)

6. Target の追加

VS 2010で作成したプロジェクトファイル(*.csproj)には、

  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->

というコメントアウトされている部分があります。この後ろあたりに次のように記述します。

  <Target Name="PostTransformAppConfig" AfterTargets="Build">
    <CallTarget Targets="TransformWebConfig" />
    <Copy Condition="Exists('$(TransformWebConfigIntermediateLocation)\transformed\App.config')" SourceFiles="$(TransformWebConfigIntermediateLocation)\transformed\App.config" DestinationFiles="$(OutputPath)\$(AssemblyName).exe.config" />
    <Copy Condition="Exists('$(TransformWebConfigIntermediateLocation)\transformed\App.config')" SourceFiles="$(TransformWebConfigIntermediateLocation)\transformed\App.config" DestinationFiles="$(OutputPath)\$(AssemblyName).vshost.exe.config" />

 ビルドが終わった後(AfterTargets="Build")に、TransformWebConfigをつかってAppConfigを変換()、その後、transformedに書き出されている変換後のファイルをリネームしつつ、アセンブリ名.exe.configと アセンブリ名.vshost.exe.config を差し替えます。

7. ビルド

これでビルドすれば、configファイルは適切に差し替わると思います。

8. 注意

AfterTargets="Build" は MSBuild 4.0 からできるようになった記述ですが、これをすると、ビルドイベント実行後(PreBuildEventより後)に作業が行われるため、たとえばビルド後にコマンドラインでファイルをアーカイブするような記述を書いていても、変換結果がアーカイブに入ってくれません。

仕方ないので、PreBuildEventに書いていたコマンドラインを PostTransformAppConfig 自体に追記することにしました。Exec タグで Command 要素を使えばコマンドラインはそのままターゲット内にかけるので、単純なものならそれで十分でしょう。条件分岐などが必要なビルドイベントは、バッチファイルをつくって呼んだ方が良いかもしれません(マクロ展開が面倒ですが)。