Home > .NET Framework, Build Toolchain, Tools > Build Transforms for App.config (and Other Xml Files), FTW!

Build Transforms for App.config (and Other Xml Files), FTW!

Like most .net devs, I fell in love the the web.config transforms that were introduced in VS 2010. And like most .net devs, I miss having them for other types of files, say, like App.configs. There is the SlowCheetah VSIX, but this imposes yet-another-build-environment-dependency on the downstream consumers of your source code. Since I’m working on a project that will ultimately be released as open source, that strikes me as one more barrier to participation, and one more thing to make automated deployment a pain. So here’s how I went about implementing it.

Looking at the target file that ASP.NET web applications use, you’ll see a long pipeline of targets that is overly complex for most non-web.config needs. In addition, the transform sections are dependent on the deploy blocks. Instead of copy-paste-munging, it’s simpler to start from scratch. Thankfully, Sayed Ibrahim Hashimi, the creator of SlowCheetah, shows us the way. We just need to solve a couple of ancillary issues and we’re done.

  1. Create your transform files, one for each build configuration you have defined. You will need to do this in the future whenever you create a new build configuration. Here’s some default .config transform boilerplate to use (adapted from the default web.config transform boilerplate):
    <?xml version="1.0"?>
    <!-- For more information on using web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
    <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
      <!--
        In the example below, the "SetAttributes" transform will change the value of "connectionString" to use "ReleaseSQLServer" only when the "Match" locator finds an atrribute "name" that has a value of "MyDB".
        
      <connectionStrings>
        <add name="MyDB" 
          connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True" 
          xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
      </connectionStrings>
    
      In the example below, the "Replace" transform will replace the entire 
      <addKey> section of your app.config file.
    
      <appSettings>
        <add key="HttpListener.Config.ListenOnUri"
          value="http://127.0.0.1:8080/"
          xdt:Transform="Replace" xdt:Locator="Match(key)"/>
      </appSettings>
      -->
    </configuration>
    
  2. Create an ItemGroup for the App.config and its subordinate transform files. Using this approach, we get the nice collapsing tree feature of the Web.config transforms in Visual Studio. We set the action to None because, as we will see below, .NET expects a file with a different name.

    Here’s what you’ll want to add:

      <ItemGroup>
        <None Include="App.config" />
        <None Include="App.Debug.config">
          <DependentUpon>App.config</DependentUpon>
        </None>
        <None Include="App.Release.config">
          <DependentUpon>App.config</DependentUpon>
          <SubType>Designer</SubType>
        </None>
      </ItemGroup>
    
  3. Uncomment the BeforeBuild Target node. The default .csproj boilerplate looks like this:
      <!-- 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>-->
    
  4. Import the TransformXml task by adding a <UsingTask> node.

      <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
    

    Yours should now look like:

      <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
      <Target Name="BeforeBuild">
      </Target>
    
  5. Add the TransformXml task. The CLR ultimately needs the config file to be named YourApp.exe.config. The name App.config is just a convention that’s understood by lots of tooling. TransformXml applies the transforms in the file for the current build configuration (e.g. App.Debug.config) to the App.config and writes out the MyApp.exe.config to the build output folder (e.g. bin\Debug\). Here’s what you’ll want to add:
        <TransformXml Source="App.config" 
                      Destination="$(OutputPath)$(TargetFileName).config" 
                      Transform="App.$(Configuration).config" 
                      ContinueOnError="False" 
                      StackTrace="True" />
    

    Yours should now look like:

      <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
      <Target Name="BeforeBuild">
        <TransformXml Source="App.config" 
                      Destination="$(OutputPath)$(TargetFileName).config" 
                      Transform="App.$(Configuration).config" 
                      ContinueOnError="False" 
                      StackTrace="True" />
      </Target>
    

    However, depending on your preferences, you could make this a multistep process. For example, if you want to keep the build output folder cleaner, especially when build errors crop up, you could do something like below. In addition, transforms are not yet supported in Mono, so if you plan on supporting Mono, you need to set the proper conditions so that you don’t throw xbuild errors. Here’s what all this looks like:

      <UsingTask TaskName="TransformXml" 
                 AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" 
                 Condition=" '$(OS)' == 'Windows_NT' "
    />
      <Target Name="BeforeBuild" Condition=" '$(OS)' == 'Windows_NT' ">
        <TransformXml Source="App.config" 
                      Destination="$(IntermediateOutputPath)$(TargetFileName).config" 
                      Transform="App.$(Configuration).config" 
                      ContinueOnError="False" 
                      StackTrace="True" />
      </Target>
      <Target Name="AfterBuild" Condition=" '$(OS)' == 'Windows_NT' ">
        <Copy SourceFiles="$(IntermediateOutputPath)$(TargetFileName).config" 
              DestinationFiles="$(OutputPath)$(TargetFileName).config" />
        <Delete Files="$(IntermediateOutputPath)$(TargetFileName).config" />
      </Target>
    

That’s it! Now when you change your build configuration, the values you get from calls like ConfigurationManager.AppSettings["YourKey"] will reflect this.

Advertisements
  1. John Smith
    02/05/2013 at 12:46 PM

    Does this method work during the build process ala SlowCheetah or will the transformations only occur during a publish operation?

    • Zack
      02/05/2013 at 1:48 PM

      Yes, these transforms occur and build time. In fact, the project types where you are mostly likely to use this technique have no “publish” tooling/functionality associated with them.

  2. Jennifer
    02/05/2013 at 5:27 PM

    This is awesome. Exactly what I needed. Thanks much!

    • Zack
      02/05/2013 at 6:27 PM

      Glad it helped! And thanks for the feedback.

    • Jennifer
      02/06/2013 at 3:51 PM

      I did have to put the TransformXml task in the AfterBuild Target instead of BeforeBuild, but otherwise worked like a charm!

  3. Leslie
    02/26/2013 at 10:19 AM

    I tried it in VS 2012 and it did not work, unfortunately. 😦 (of course I changed the dll’s path to v11.0) Nice feature though…

    • Zack
      02/26/2013 at 10:33 AM

      I’ll take a look at it in 2012 and see if we can’t get it working again. Thanks for pointing this out.

  4. Wade
    02/28/2013 at 2:14 PM

    This worked for me in VS 2012. Very nice.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: