Environment specific config files for .NET apps – Config Transforms

Hello World,

This post is about an area that I for one had little experience with until I was on a project that required me to understand ways to maintain environment specific config files for our application.

The Problem

Very succinctly, the problem I had was that for my .NET app, we had to find a way to automate the generation of environment specific config files (web.config OR app.config). Now most of us have encountered situations where our config files need some changes when the application is deployed in a test or in the production environment. For example, you’d need to change the connection strings, remove the debug flag (in web apps) and the like. The trivial (and very ugly and hard to maintain) solution is to create a complete config file per environment and then, (phew) every time there are changes or modifications, you must manually (remember to) change all the other files so that nothing shockingly breaks on deployment. Fortunately, better and more robust ways do exist, and that’s what we’ll see.

Solution 1: The Config Transform Tool

The config transform tool or ctt derives from config transforms introduced with VS 2010 for web applications. In VS 2010 and above, whenever you created an ASP.NET application, you could see the web.config had two related files: web.debug.config and web.release.config:

proj

If you open one of web.debug.config or web.release.config, you’d notice (by default) a sample transform for the connectionStrings section like so:

<connectionStrings>
      <add name="MyDB" 
        connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True" 
        xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</connectionStrings>

This looks pretty much like the standard connectionStrings section except the two new attributes, xdt:Transform and xdt:Locator. These two attributes tell the XDT transform engine to “SetAttributes” and match the attribute to set by its name as Locator is specified to be name.

In other words, this is what happens: read the web.config connectionStrings section, read the <add> node and set the name and connectionString attributes to the values provided in the transform file (the web.debug.config or web.release.config – the selection of the transform file itself is based on build configuration chosen.)

MSDN provides complete syntax reference for the possible transformations possible. You can insert, modify, or remove attributes or elements as you please.

In its default avatar, this tool is nice, but tied to build configurations and there’s no easy way to adapt this to and environment specific config (unless you want to configure a build configuration per environment – but that’s messy IMHO). Another shortcoming out-of-the-box is that it only works for web config files. Fortunately, Dennis Gladkikh wrote the config transform command line tool that can integrate with the build process and use the same XDT syntax to produce environment specific files from a given base config file. (actually, you can transform any XML file!).

All it takes is this command:

ctt.exe s:"source.config" t:"transform.config" d:"destination.config"

You can then add one such line for each environment that you have in your post build event like so:

ECHO Generating environment-specific config files...

SET transformTool=$(SolutionDir)ConfigTransformationTool\ctt.exe
SET webConfig=$(ProjectDir)Web.config
SET nlogConfig=$(ProjectDir)NLog.config 
SET configDirMain=$(ProjectDir)Config

"%transformTool%" s:"%webConfig%" t:"%configDirMain%\Web.Dev.config.xdt" d:"%configDirMain%\Web.Dev.config"
"%transformTool%" s:"%webConfig%" t:"%configDirMain%\Web.Live.config.xdt" d:"%configDirMain%\Web.Live.config"
"%transformTool%" s:"%webConfig%" t:"%configDirMain%\Web.Test.config.xdt" d:"%configDirMain%\Web.Test.config"

"%transformTool%" s:"%nlogConfig%" t:"%configDirMain%\NLog.Dev.config.xdt" d:"%configDirMain%\NLog.Dev.config"
"%transformTool%" s:"%nlogConfig%" t:"%configDirMain%\NLog.Live.config.xdt" d:"%configDirMain%\NLog.Live.config"
"%transformTool%" s:"%nlogConfig%" t:"%configDirMain%\NLog.Test.config.xdt" d:"%configDirMain%\NLog.Test.config"

REM Do not accept errors in the above statements.
IF ERRORLEVEL 1 EXIT 1

Notice that the last line “IF ERRORLEVEL 1 EXIT 1” is a safety net in that if there is an exception thrown by the transform tool, your build will break (a good thing in this case!)

NOTE 1: the variables surrounded by $() are macros defined by visual studio/MSBuild and can be seen in the Build events tab by clicking on the “Edit Post-build” button:

options

NOTE 2: You would need to download ctt.exe from CodePlex and keep it in a directory that you can refer to when the build runs.

Some Gotchas!

Gotcha 1: You cannot test for an element and do something if it exists

There is no way (as of now) to check if an XML element exists, and if found, do something with it. What you can safely     do instead is use xdt:Transform=”RemoveAll” and then xdt:Transform=”Insert”. You will see a warning if the element is not found.

Gotcha 2: Custom xmlns breaks transforms!

If your config file uses a config section that has it’s own schema and you provide that schema to enable intellisense on your custom sections, you are in for a surprise! The config tool will silently (in the default log verbosity) ignore you custom config elements during transformations. The way to work around this is to qualify each element in your transform file with an alias and declare that alias at the root/document element – in case of .NET config files, this is the <configuration> element.

So at the top of the config file, you declare your schema’s alias:

<configuration 
         xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"             xmlns:myApiAlias="http://www.mycompany.com/myapi/schemas/MyApiConfiguration.xsd">
<configSections>
		<sectionGroup name="myApi">
			<section name="sso" type="dummy" xdt:Locator="Match(name)" xdt:Transform="Remove"/>
			<section name="sso" type="MyApi.Services.Configuration.SsoConfiguration, MyApi.Services.Root.Common" xdt:Transform="Insert" />
		</sectionGroup>
	</configSections>

And then, in the custom config section, qualify each element by this alias:

<myApiAlias:myApi>
   <myApiAlias:systemEnvironment type="Live" xdt:Transform="SetAttributes(type)" />
</myApiAlias:myApi>

 Gotcha 3: Write Tests to ensure transforms have worked!

The best way to ensure that your transforms are working is to write tests using your favorite Unit Test framework that check them. The test would basically work like so:

  1. Open you web/app.config and return the System.Configuration.Configuration object containing the file contents
  2. Open each of the transformed files and return their System.Configuration.Configuration object
  3. Read the values from a section in the original config and compare with values read from the transformed file – followed by Assert statements to validate if the transform succeeded.

Solution 2: Slow Cheetah (love the name)

Slow Cheetah is a free visual studio plugin that adds a lot of GUI help to the process of creating transform files. For example, you can right click the web.config file and choose to “Add Transform”. Once done with the transforms, you can right click on a transform file and select “Preview Transform” to see how your generated config file would look like once the transforms have been applied. I’m yet to try out the features offered by Slow Cheetah, so I’ll try to cover it in detail in a later post.

That’s it for now, until next time,

Happy Coding!

Advertisements
Tagged with: , ,
Posted in .NET, C#, Visual Studio

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

  • Comic for March 28, 2017
    Dilbert readers - Please visit Dilbert.com to read this feature. Due to changes with our feeds, we are now making this RSS feed a link to Dilbert.com.
%d bloggers like this: