Transforming an App.config file
The first thing you’ll notice as you start working with ASP.NET 4, is that they’ve finally solved the problem with deploying web.config files. This is a major time sink in our team as we spend hours every sprint updating configuration files on our environments. Not anymore, right!?
Wrong!
They only solved the problem for web.config. There’s still no way to do the same thing (that I’ve found) for other XML-based configuration files. If you’re like me, you probably like to extract configuration from web.config into other files (like log4net.config) to make it more manageable. These do however not work under the .NET 4 Web Deployment Tool.
Do it with XSLT
What Microsoft has created is a simplified version of XSLT. They have however also supplied the solution to our problem in the latest version of their build system MsBuild. The task is called XslTransformation and resides in Microsoft.Build.Tasks.v4.0.dll.
The problem of different environments
My problem is that I work with the same project on different computers, and I have a continuous integration mechanism that deploys my solution continuously to a test environment. Every environment has its own database connectionstring, and further on they will have different configurations for logging, caching, etc.
The web deployment tool will take care of individual changes for each environment concerning web.config, but my integration test project stores its database connection string in App.config which leaves me totally screwed.
The solution is to specify indiviual changes for each environment
My App.config looks like this.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="ApplicationDatabase"
connectionString="Data Source=SERVER\SQLEXPRESS;Initial Catalog=ApplicationDatabase;Integrated Security=True;"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
And I create change files for each and every computer that I have. The name between App and config is the name of the environment, or rather the value of $(Computer) in MSBuild. If you have several environments on the same machine you might need to start using Build Configurations in Visual Studio and select your configuration change file on that.
Now, let’s look at what that MAIA (my main developer machine) configuration looks like.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<!-- Default template -->
<xsl:template match="node()|@*">
<xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>
</xsl:template>
<!-- Connection string replacement template -->
<xsl:template match="/configuration/connectionStrings/add[@name='ApplicationDatabase']">
<add name="ApplicationDatabase"
connectionString="Data Source=MAIA\SQLEXPRESS;Initial Catalog=ApplicationDatabase;Integrated Security=True;"
providerName="System.Data.SqlClient" />
</xsl:template>
</xsl:stylesheet>
The first part after the XML declaration means, “match every node and attribute and apply a template that matches” which will be recursive since that template matches just about everything.
If you write another template matching something more precise like the connectionString configuration, that will override the base rule and let us change the contents of the node that we’re matching.
This is now applied before every build I make. This is done by opening up the csproj-file (which is a msbuild script) and manually adding the following at the end.
<Target Name="ApplyMachineSpecificConfiguration" Condition="Exists('App.$(Computername).config')">
<XslTransformation XmlInputPaths="App.config" XslInputPath="App.$(Computername).config" OutputPaths="App.config_output" />
<Copy SourceFiles="App.config_output" DestinationFiles="App.config" />
</Target>
<Target Name="BeforeBuild">
<CallTarget Targets="ApplyMachineSpecificConfiguration"/>
</Target>
The target BeforeBuild is by default run before any compilation and that is where we call our target that will apply the XSL on App.config. We store the result in a temporary file, because the XslTransformation does not like it when we try to overwrite the XML-file that we’re reading from.
Now I can edit all configuration from one place and do not have to worry about manually merging between environments anymore. Sweet!
Inspiration to this article came mainly from Fredrik Knutson.
10 Comments
This entry is filed under Programming and tagged with .net 4, configuration, msbuild, web deployment tool.
You can also follow any responses to this entry through the RSS 2.0 feed.
Or perhaps you're just looking for the trackback and/or the permalink.


Nice!
We’ve integrated this in our builds on a build server and have different transformation files depending on the build configuation currently beeing built. In addition we’ve set up different build configurations for our different deployment environments, TEST, PROD and so on.
/Fredrik
There is also away to get this working with eariler versions of .NET using the Xslt task from msbuild community tasks. Great if you’re not on a .NET 4 project.
http://msbuildtasks.tigris.org/
Mikael
Thanks Mikael and Fredrik for sharing this info! I just recently tried this out but am getting the error:
Unable to copy file “App.config_output” to “App.config”. Access to the path ‘App.config’ is denied.
Thoughts?
I did run into that while attempting this on a TFS project. The problem then was not that I didn’t have access to App.config, but rather that TFS made it readonly as it where not checked out. I solved it with the attrib task to remove the readonly attribute on the file, copy over the file and add the readonly attribute again to make sure that TFS does not warn me that the file has been changed outside the control of TFS. I don’t have the code here but it looks something like this
<Attrib Files="App.config" ReadOnly="false" />
<Copy SourceFiles="App.config_output" DestinationFiles="App.config" />
<Attrib Files="App.config" ReadOnly="true" />
The Attrib task seems to be a part of the msbuild community tasks. You’ll find them here: http://msbuildtasks.tigris.org/
Hope this will solve your problem.
Nice idea,
but won’t this lead to endless version conflicts on a team? You want *.config versioned, but with all devs rewriting it every build, you’ll get conflicts often, and real changes that are not caused by automatic rewriting may easily be lost?
@Robert On my current project we don’t need unique configuration for every developer, but only for the different environments development, test, staging, production. This works like charm, because we treat the configuration files as any other piece of code. On commit, the correct transformation file is chosen for the target environment and transformed.
Using different configuration transform files for each and every developer on the team, could also work, but there would be a risk that your transformation file for your indiviual development environment could get outdated when the base configuration changes dramatically, like when refactoring.
Right now, we have a “default” transformation that we apply first, that contains all the defaults. Then we transform the specific environment changes to that default. We’ve been using it for 1 month on 8 different environments without any problems.
A slight improvement (well for me anyway): rather do it AfterBuild and have it update the output .config. This avoids the issue of checking out and/or changing app.config.
Great concept! Have been doing something similar using XSLT, but your way is much better than mine.
I solve this problem with little tool http://ctt.codeplex.com/ which I wrote and which I use with CCNet for making packages.
[...] use that to encrypt your web.config. By having those unencrypted versions in version control, and building fresh configuration files for every deploy – it becomes quite a blast to let msbuild encrypt sensitive data just before [...]
[...] use that to encrypt your web.config. By having those unencrypted versions in version control, and building fresh configuration files for every deploy – it becomes quite a blast to let msbuild encrypt sensitive data just before [...]