I need the ability to change specific values in the test and production (release) environments. For instance, the connection string is obviously different, and I would also like to turn off logging and debugging in production.

I’m using a Web Deployment Project, so I have MSBuild at my disposal. Now, the WDP comes with an option to replace specific configuration sections, but that option is not powerful enough, as it requires you to replace an entire configuration section with another. For example, in the above configuration file I might want to change only the EnableCaching property. I don’t want to keep 3 versions of the same appSettings section, only to be able to change just that single property.

What I needed turned out to be in the MSBuild Community Tasks project. Specifically, it contains a very powerful task called XmlMassUpdate. This task allows me to specify a substitution file that looks like this:

As you can see, for debug I’m leaving everything as it is. For Test and Release I’m changing some of the properties. I want logging on in the Test environment, so I don’t even mention it in my substitutions file, but for production it has to be changed to false. Other than that, everything here is pretty straight forward. I change only what I need to.

One thing to notice, though, is that the appSettings values have an attribute called xmu:key. This attribute is needed for the replacement engine to know by which attribute to locate the value to replace. For example, look at the first substitution in the test environment:

<add xmu:key="key" key="GisServer" value="134.122.34.3"/>

We are telling XmlMassUpdate to find the GisServer value in the original web.config file, and replace it with this one. But XmlMassUpdate needs to know how to match our substitution value with the original value, so by saying xmu:key=key we’re telling it to compare them by the key attribute. This way, it will know that it should look for an appSettings setting with a key=”GisServer” attribute.

Once the substitution file is ready, we have to edit the MSBuild project file to call XmlMassUpdate. For that you would first need to right click your Web Deployment project and hit “Open Project File”. Than you have to add the following lines to it:

First we import the MSBuild Community Task target file. This will allow us to use the XmlMassUpdate task (of course you have to install the community tasks project first for this to work).

In lines 3-6 we’re adding a build event called MyAfterBuild. In fact the WDP project already contains a target called AfterBuild, but I found out that you can’t really use it. If you specify an AfterBuild target, you will override the WDP AfterBuild target, which actually copies the web site files to the target directory. If you override that, well, your build won’t work. So I’m specifying that the build should also depend on my MyAfterBuild target.

In line 7 I’m specifying that my substitution file resides in the original source web, in a file called substitutions.xml.

In line 10 I’m making sure the substitution file will not be deployed together with the web site (might be a bad idea to deploy all these connection strings).

In lines 12-18 we’re finally calling XmlMassUpdate.

ContentFile tells it where to find the web.config to edit (in the output location, usually).

SubstitutionsFile tells it where to find our substitutions.xml.

ContentRoot tells it that in the original web.config, the xml path to start replacing stuff at is the configuration node.

SubstitutionsRoot tells it that in our substitutions.xml, the task should take values to replace from the specified xml path. For example, when we compile in debug it will take values from /configuration/substitutions/debug, and when we compile in release it will be taken from /configuration/substitutions/release. In order to enable our Test environment substitutions, we will have to add another build configuration. That’s as easy as going to Build->Configuration Manager and adding a new solution configuration called Test.

And there you go. Save and recompile your project, and you will find that the web.config in your output folder has the correct values, depending on the build configuration you chose.

Go ahead and download the MSBuild Community Tasks now. They have a pretty good documentation, which helped me a lot in finding this elegant solution and writing this post.

19 comments

Hi,
It does not seem that the $(OutputPath) is returning the correct path to my _publishedwebsites\ITMonitoring_deploy directory. I tried changing it to point to the source, but as TFSBuild is only doing a get on the files, they are read only at the time. In the deployment directory, the file are not read only, so it would work. Problem is that the outputpath is not pointing to the correct place, so i get a file not found. Any ideas? I am using a web site rather than a web project, and have added a web deployment project to it.

This was a great walkthrough on the initially-confusing XmlMassUpdate task. Thanks for getting me up to speed!

One thing I discovered was that using the ExcludeFromBuild item triggers an extra site copy operation that almost doubles the build time. Instead I used a Delete task after the XmlMassUpdate to delete the substitutions file from the output folder, and my build is a lot faster now.

Hey Juan,
I’m not sure why is that the case for you, but you can point the task to wherever the web.config is. If OutputPath doesn’t work there is probably some other property you can use, or you can simply try something like $(OutputPath)\..\web.config.

I saw that you recently commented on Matt Berseth’s blog here: http://mattberseth.com/blog/2007/05/single_config_file_multiple_de.html. You said that xmlMassUpdate could achieve what he was trying to do. Your example here shows how to substitute static sections of web.config but what if the substitution you wanted to make was dynamic. Such as inserting the svnVersion that is retrieved during the MSBuild into web.config as an appSetting. How would you accomplish this?

Changing “key” to “name” did it. Because the examples were all for the appSettings section, I made the assumption that after you specified the key name “xmu:key=”name””, that you would then specify the value for that key with key=… Turns out you don’t do anything special for that. I was overthinking it!