Tuesday, February 7, 2012

Producing multiple versions of Web.config file

What is the best way of preparing configuration files for different project environments? Is it worth investing resources in creating more automatic and transparent build process? Does it make any sense for a project that is going to be installed only once into production environment?

From time to time I have discussion similar to this with somebody - my team lead, my project colleagues and the most popular answer to the questions mentioned above was Yes for a product and Partially Yes for a project. Partially usually meant the following steps:

  • retrieving source code from the repository
  • building the project

No deployment to the environment locations, no merging of configuration files. Basically the build process is something that comes out of the box with some minor adjustments.

My personal opinion is Yes for both cases (project and product) and let me put a short note why.

Usually production environment is not the first and the single target were the project is going to be deployed. In many cases the bits to be pushed to the client flow through a bunch of other environments - development, testing, staging, so having some automated build procedure isn't just helpful - it is a must. It does not matter whether you are deploying a project or a product - the build process should be complete and any manual intervention of the person performing a build should be avoided. Copying build files to the server using Total Commander, tracking configuration file changes using WinMerge - no, all this should be a part of the automatic build process. Any manual intervention is a possible source for a human error - something can be skipped, something mistyped. On the other hand, it is often a requirement to have frequent builds that should be deployed on demand (for example: fixing a critical issue) so managing something for every build by hand would be painful.

One of the major challenges faced in the projects I was involved was the requirement to prepare different configuration files for different environments during the build process. Usually changes used to be merged into multiple configuration files manually. Any alternatives? Let me share one of them.

Microsoft Visual Studio 10 allows you to prepare multiple templates for configuration files. Basically you would have a single master configuration file and the child templates containing only the specific parts of the configuration that has to be applied to the specific configuration file. What can be achieved with templates? 

  • It is possible to remove/add/replace configuration file elements
  • It is possible to set configuration element attributes to some environment specific values
  • Templates can be transformed into configuration files during the build process 

 Possible usage scenarios:

  • setting different appSettings values for different environments
  • setting different connectionStrings values for different environments

and similar.

A sample of the Web.config transformation template:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <connectionStrings>
        <add name="ApplicationServices"
            connectionString="Data Source=DebugSQLServer;Initial Catalog=MyDebugDB;Integrated Security=True"
            xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
    </connectionStrings>
    <system.web>
        <customErrors defaultRedirect="GenericError.htm"
        mode="RemoteOnly" xdt:Transform="Insert">
            <error statusCode="500" redirect="InternalError.htm"/>
        </customErrors>
    </system.web>
</configuration>

The above mentioned template serves for two purposes:

  • It replaces ApplicationServices connection string with a value specific to the particular environment
  • It inserts customErrors section into the transformed configuration file which is also specific to the particular environment. 

When writing a configuration file template you should only care about two things: how to LOCATE a master configuration file part to be changed and how to actually TRANSFORM it.

Element search conditions can be defined as xdt:Locator attribute values (xdt stands as a prefix for transformation syntax namespace http://schemas.microsoft.com/XML-Document-Transform). Conditions can include XPath expressions, comma-separated attribute values, locator functions and more. In the example mentioned above the Match(name) function is used to locate connectionString element in the master configuration file that has its name attribute set to ApplicationServices.

Element transformation mode can be defined as xdt:Transform attribute values. Some examples: SetAttributes - set located element attributes to the specified values, Insert - add specified element and others.

More information about template file syntax is available here: Web.config Transformation Syntax for Web Application Project Deployment.

The final thing - how to apply the created templates and produce transformed configuration files during build process?

When you are building a project using Visual Studio, you are actually using the msbuild to achieve this. The *.csproj it self is actually a msbuild script. The configuration file transformation is nothing more than an additional build step that needs to be defined in the script.

I am not an expert of msbuild and creating fluent shiny build scripts is not my strongest part, so I will give you a short note how you could modify your existing web site *.csproj project to perform configuration file transformation and copy results to the output directory.

Add following XML fragment into your *.csproj file just before closing project element:

<Project>
...
    <UsingTask 
        TaskName="TransformXml" 
        AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.Dll"/>
</Project>

And add the following lines under the <target name="AfterBuild">

<TransformXml Source="web.config" Transform="web.debug.Config" Destination="bin\web.debug.Config" />
<TransformXml Source="web.config" Transform="web.release.Config" Destination="bin\web.release.Config" />

What is going to happen here during the build process (when building a project using Visual Studio or using msbuild directly) is that web.config file content is going to be transformed using templates web.debug.config and web.release.config and the output is going to be saved into BIN directory under the same names.

To me all this looks like a painless way to transform configuration files the way I want for any environment. Does it look so to you too?