Jay Harris is Cpt. LoadTest

a .net developers blog on improving user experience of humans and coders
Home | About | Speaking | Contact | Archives | RSS
 
Filed under: NAnt | Task Automation | Tools

Scott Hanselman posted an entry yesterday about Managing Multiple Configuration File Environments with Pre-build Events. His design uses pre-build events in Visual Studio to copy specific configuration files to the default file name, such as having "web.config.debug" and a "web.config.release" configuration files and the pre-build copying the appropriate file to "web.config" based on which build configuration you are in. This is a great idea, but large web.config files can get tedious to maintain, and there is a lot of repeated code. Even using include files as Scott suggests would help, but major blocks, such as Application Settings, may have to be repeated even though only the values change. This exposes human error, since an app setting may be forgotten or misspelled in one of your web.config versions.

At Latitude, we manage this problem through NAnt. One of our former developers, Erik Nelsestuen–brilliant guy–authored the original version of what we call "ConfigMerge". Essentially, our projects have no web.config under source control. Instead, we have a web.format.config. The format config is nearly identical to the web.config, except all of the application settings and connection strings have been replaced with NAnt property strings. Rather than have a seperate web.config for each environment and build configuration, we simply have NAnt property files. Our build events (as well as our automated build scripts) pass the location of the format file and the location of the property file and the output is a valid web.config, with the NAnt property strings replaced with their values from the environment property file.

It's simple. It only takes one NAnt COPY command.

default.build

<project default="configMerge">
  <property name="destinationfile"
    value="web.config" overwrite="false" />
  <property name="propertyfile"
    value="invalid.file" overwrite="false" />
  <property name="sourcefile"
    value="web.format.config" overwrite="false" />
 
  <include buildfile="${propertyfile}" failonerror="false"
    unless="${string::contains(propertyfile, 'invalid.file')}" />
 
  <target name="configMerge">
    <copy file="${sourcefile}"
        tofile="${destinationfile}" overwrite="true">
      <filterchain>
        <expandproperties />
      </filterchain>
    </copy>
  </target>
</project>

For an example, lets start with a partial web.config, just so you get the idea. I've stripped out most of the goo from a basic web.config, and am left with this:

web.confg

<configuration>
  <system.web>
    <compilation defaultLanguage="c#" debug="true" />
    <customErrors mode="RemoteOnly" /> 
  </system.web>
</configuration>

In a debug environment, we may want to enable debugging and turn off custom errors, but in release mode disable debugging and turn on RemoteOnly custom errors. The first thing we will need to do is create a format file, and then convert the values that we want to make dynamic into NAnt property strings.

web.format.config

<configuration>
  <system.web>
    <compilation defaultLanguage="c#" debug="${debugValue}" />
    <customErrors mode="${customErrorsValue}" /> 
  </system.web>
</configuration>

Next, we need to make NAnt property files, and add in values for each NAnt property that we've created. These property files will include the values to be injected into the web.config output. For the sake of simplicity, I always give my property files a '.property' file extension, but nant will accept any file name; you can use '.foo' if you like.

debugBuild.property

<project>
   <property name="debugValue" value="true" />
   <property name="configMergeValue" value="Off" />
</project>

releaseBuild.property

<project>
   <property name="debugValue" value="false" />
   <property name="configMergeValue" value="RemoteOnly" />
</project>

Finally, we just execute the NAnt script, passing in the appropriate source, destination and property file locations to produce our environment-specific web.config.

nant configMerge -D:sourcefile=web.format.config -D:propertyfile=debugBuild.property -D:destinationfile=web.config
web.config output

<configuration>
  <system.web>
    <compilation defaultLanguage="c#" debug="true" />
    <customErrors mode="Off" /> 
  </system.web>
</configuration>

And that's all there is to it. This is extendable further using the powers of NAnt. Using NAnt includes, you can put all of your base or default values in one property file that's referenced in your debugBuild.property or releaseBuild.property, further minimizing code. You can use Scott's pre-build event idea to have each build configuration have its own NAnt command to make mode-specific configuration files.

Feel free to use the above NAnt script however you like; but, as always YMMV. Use it at your own risk.

Enjoy.

Friday, 21 September 2007 22:00:26 (Eastern Daylight Time, UTC-04:00)  #    Comments [5] - Trackback

Sunday, 21 December 2008 11:28:31 (Eastern Standard Time, UTC-05:00)
Your nant command:

nant ConfigMerge -D:sourcefile=web.format.config -D:propertyfile=debugBuild.property -D:destinationfile=web.config

throws an error as “ConfigMerge” should actually be “configMerge” since your build file has a lower-case “c”.
Sean Kenney
Monday, 22 December 2008 09:35:51 (Eastern Standard Time, UTC-05:00)
Thanks, Sean, for pointing that out. You are absolutely right. I have updated the post to reflect this.
Wednesday, 08 April 2009 12:51:08 (Eastern Daylight Time, UTC-04:00)
This article was exactly what I was looking for, thanks!
Thursday, 25 June 2009 16:53:28 (Eastern Daylight Time, UTC-04:00)
This really worked with some minor changes.

Thanks for posting this.

Giri.
Giri Shreenivasan
Tuesday, 17 November 2009 05:14:15 (Eastern Standard Time, UTC-05:00)
Hello, I agree about the web.configs bit, I like your approcah I prefer cleaner and if possible less code.
Regards
Galilea

BRW I'm not a great programmer, anyway.
OpenID
Please login with either your OpenID above, or your details below.
Name
E-mail
(will show your gravatar icon)
Home page

Comment (HTML not allowed)  

[Captcha]Enter the code shown (prevents robots):

Live Comment Preview