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.