by Joakim

Automatic Config Transformations

My fellow blogger here on the Degree Blog, Thor Halvor, had a blog post not too long ago titled “Don’t let the Configuration transform your Configs” where he recommends that people don’t transform their config files based on the solution configuration, and he points out a lot of things which is important to keep in mind. I, however, think that using the solution configurations to control which transformation files are applied to your config files works quite well.

As Thor Halvor points out in his blog post, you get a few extra config files when you create a web application in Visual Studio, namely Web.Debug.config, and Web.Release.config. These files are the XML transform files for Web.config, and when you build you application locally, they do exactly nothing (no matter if you have selected the debug or release solution configuration). If you use the “Build Deployment Package” or the “Publish” functionality in Visual Studio however, they are applied to your web.config file based on the currently selected solution configuration. Now this might work fine for a small personal project like your own homepage etc., but I doubt many people use this on large work projects. Another problem with this functionality (as Thor Halvor points out), is that it only works for Web.config, and in larger projects you often have more than one config-file (connection strings in a separate file, etc.).

So how can we use xml transform files for more than Web.config, and have the transformation be performed as part of an automated build on a build server? By adding a new target to the Web Application’s project file (.csproj) which will run as a part of the build (You need to right click on the project and choose “Edit project file”).

<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  .
  .
  .
  <!-- 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="TransformConfigFiles" AfterTargets="AfterBuild" Condition="'$(TransformConfigFiles)'=='true'">
    <ItemGroup>
      <DeleteAfterBuild Include="$(WebProjectOutputDir)Web.*.config" />
      <DeleteAfterBuild Include="$(WebProjectOutputDir)ConnectionStrings.*.config" />
    </ItemGroup>
    <TransformXml Source="Web.config" Transform="$(ProjectConfigTransformFileName)" Destination="$(WebProjectOutputDir)Web.config" />
    <TransformXml Source="ConnectionStrings.config" Transform="ConnectionStrings.$(Configuration).config" Destination="$(WebProjectOutputDir)ConnectionStrings.config" />
    <Delete Files="@(DeleteAfterBuild)" />
  </Target>
  .
  .
  .
</Project>

This new target will run after the “AfterBuild”-target, and it will transform both Web.config and ConnectionStrings.config, given that you specify the property “TransformConfigFiles” to be “true” (because we don’t want this be be done when we’re developing the application and building it in Visual Studio. As you can see above it uses the “Configuration”-property to decide which xml transform file to use (this property will contain the name of the selected solution configuration). After performing the transformation, it will clean-up/delete the xml transform files.

ConfigTransform1As you can see, I have created an additional config file, namely ConnectionStrings.config, as well as xml transform files for it (ConnectionStrings.Debug.config and ConnectionStrings.Release.config). The transformation files I’ve created aren’t group under the actual config file like Web.config’’s xml transformation files are, but if you wanted to you could edit the project file to do this.

You need to have xml transformation files for each solution configuration for those config files you want to transform, and they must all be named as <ConfigFileName>.<SolutionConfigurationName.config. I.e. if you create a new solution configuration called “Production” you would need to create a xml transform file called ConnectionString.Production.config (for Web.config you would just need to right click on it and select “Add Config Transforms”.

 

 

 

Inside my transformation files I’ve just put some simple transformations that will allow me to see that the config files are actually being transformed.

Web.config:
<configuration>
  <connectionStrings configSource="ConnectionStrings.config" />
  <appSettings>
    <add key="Configuration" value="" />
    ...
  </appSettings>
  ...
</configuration>

Web.Release.config:
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add key="Configuration" value="Release" xdt:Transform="Replace" xdt:Locator="Match(key)" />
  </appSettings>
  </system.web>
</configuration>

ConnectionStrings.config:
<connectionStrings>
  <add name="DefaultConnection" connectionString="Data Source=(local);Initial Catalog=dbMVC4;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>

ConnectionStrings.Release.config:
<connectionStrings xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <add name="DefaultConnection" connectionString="Data Source=PreProdDatabaseServer;Initial Catalog=dbMVC4;Integrated Security=True" providerName="System.Data.SqlClient" xdt:Transform="Replace" xdt:Locator="Match(name)" />
</connectionStrings>

In order to build the Web Application and have the config transformations take place we must run msbuild and specify the solution configuration we want to use, and that TransformConfigFiles should be true.

Running msbuild manually from the Visual Studio command prompt:

msbuild "C:...Testing.sln" /p:Configuration=Release /p:TransformConfigFiles=true /p:OutDir=C:TempTesting_Release

Under “C:TempTesting_Release_PublishedWebsitesMVC4” one should be able to find the Web Application with transformed config-files.

Now for the cool part, this approach will also work on your TFS build server, in your build definition just specify the build configuration and add TransformConfigFiles=true to the MSBuild Arguments.

ConfigTransformTFS

On the project I’m currently working on, we have successfully been using this solution with TFS 2010 to automatically build and deploy our web application to the different testing environments for the better part of a year now! Smile

  • thorhalvor

    Hi, thanks for correcting me. When you wrote “These files are the XML transform files for Web.config, and when you build you application locally, they do exactly nothing”, I thought WTF.. But, you are correct. It was actually a another developer that put the transformation in all our AfterBuilds. Almost the same way you did but without the “TransformConfigFiles true/false” check… And I thought it was a out of the box thing..

    So then I agree with most of what you say. But I don’t understand why you and other people switch configfile based on Configuration. That is “Release” and “Debug”. Debug != Test and Release != Production. What about Staging environment? What if you want Relase dll’s in Test? or Debug in Production?

    I would strongly recommend to atleast introduce a variable “Environment” or something which can be Test/Staging/Production and call you configs accordingly.

  • http://www.degree.no Joakim

    @thorhalvor: Yes, you could definitively introduce another variable like “Environment” and make use of this instead of “Configuration” inside the “TransformConfigFiles”-target I’ve shown above, or you could create separate “Solution Configurations” for them. Both would be valid approaches I think, and comes down to preferences.

    On larger projects you probably have multiple branches as well, and I feel that having multiple branches in combination with 2-3 solution configurations usually gives me enough flexibility to build all the test environments etc. needed. So on the project I’m currently working on for example, our pre-production test server (staging server if you will) is built from the production-branch using the release-configuration (production is also built from the production branch, of course, but using a solution configuration we’ve created called “package”, as this one is not auto-deployed).

    I get your point where you say that you may want release dll’s in test, debug dll’s in production, etc., and this could mean that we would have to create a LOT of different solution configurations. Now for our test environments we actually have 2 for each branch, one built with the debug-configuration and one built with the release-configuration, but putting debug dll’s in production isn’t covered by any of our automatic builds. I do however feel that the latter is a special case, and I’m OK with this being a manual job (if I have to do this a lot something is wrong with our testing regime), i.e. I would just check out the changeset in question from the production-branch, build the dll’s I need debug versions of, and manually copy those dll’s to production (I shouldn’t need to touch the config-files).

  • http://www.forse.no Andreas Knudsen

    I find it helpful to separate the configuration “dimension space” from the build configuration “dimension space”. If you have a web site which is to be deployed in 3 environments, and in each of them you install it 2 times (load balanced), then conceptually you might need to have 6 different config files (in addition to the one(s) used in development). If you tie this to build configuration then you would need to recompile the same codebase 6 times just to produce the correct amount of config files. This seems wasteful to me. Instead I generate the 6 config files on each and every build, and then do some file-swapping-fu in my deploy-scripts. I use T4 to generate the config files, it works like a charm both locally and on the build server.

  • Pingback: Integração contínua TFS/Azure e connection string em app.config - DL-UAT()

  • Anonymous

    God bless the poster for ConnectionString.config build targets.
    Saved my day.