Testing Web.config Transformations, Part 1

Web.config transformations is a Microsoft-supported technology for adapting a base configuration to a particular deployment environment.

In my previous post, I mentioned how AppHarbor provides an online tool for manually testing transformations. Also, the Visual Studio extension SlowCheetah provides support for manually testing and transformation and diffing them against the base configuration from inside Visual Studio. These tools are useful and may be fine for tinkering with getting a transform just right, but being software professionals, we find it highly desirable to reliably automate our transformation testing.

In this article we shall look at one way to exercise our Web.config transformations from our own code, so that we can TDD our transformations and regression test them as part of our continuous integration cycle, like everything else.

Starting Point

The actual Web.config transformation process is normally invoked by MSBuild, as described in detail by Elton Stoneman in this post. (One approach to testing the transformations could, thus, be to fork off MSBuild processes on custom build targets, but we’d like to take a less cumbersome route.)

Being an MSBuild task means that the transformation is implemented as an ITask realization. The concrete implementation is called TransformXml and resides in the Microsoft.Web.Publishing.Tasks.dll assembly. Note that this assembly is not pre-installed with Windows/.NET but rather installed along with Visual Studio 2010, typically to C:Program Files (x86)MSBuildMicrosoftVisualStudiov10.0Web). Given that its location is not fixed, you’d probably want to add it to your dependencies folder (caveat emptor: check license terms before passing it around).

From Elton’s post, we can also see 3 arguments being passed to the TransformXml task:

  • Source (the XML file to transform)
  • Transform (the transformation XML file itself)
  • Destination (the target of the transformed XML file)

Armed with that background knowledge, let’s move on and try to get the transformation to run outside of the MSBuild environment in which it normally executes. For our experiment, we’ll be transforming the following source (app.config):

The transformation we’ll be testing (app.Debug.config) should change the “testValue” of the “testKey” to something transformation specific (“debugValue” in this case):

Test Approach

Our initial stab at automatically testing the transformation will be to invoke the XmlTransform through its outermost interface; i.e. the ITask interface. First, we need to add a reference to Microsoft.Web.Publishing.Tasks.dll as mentioned above. Additionally, we need to reference Microsoft.Build.Framework and Microsoft.Build.Utilities.v4.0 (these should be available from your usual Project, Add Reference, .NET tab).

Opening up the XmlTransform class in Visual Studio’s Object Browser shows us a single method, Execute(), plus a handful of properties, most of which we recognize from Stoneman’s previously mentioned post. A little experimentation reveals the following details:

  • SourceRootPath
    • The base path to which the XML file specified in Source is relative. If not specified, this will  be the current directory (which, when running from a test runner, would typically be your build target directory).
  • Source
    • The name of the XML source file to be transformed. If this is not an absolute path, it will be relative to the SourceRootPath.
  • TransformRootPath
    • The base path to which the XML file specified in Transform is relative. If not specified, this will be the same as SourceRootPath.
  • Transform
    • The name of the XML transformation to be applied to the source. If this is not an absolute path, it will be relative to the TransformRootPath.
  • Destination
    • The name of the resulting transformed XML file. If this is not an absolute path, it will be relative to the current directory (typically your build target directory, in a test context).

So all we need to do is something like the following, right?

Well, as the System.InvalidOperationException with the message “Task attempted to log before it was initialized” thrown in our face subtly tells us, this is not quite the case…

Investigating the task implementation with Reflector shows the problem to be due to the fact that a property, BuildEngine, of the Task base class has not been set. It is used as a dependency injection mechanism, giving the task access to some environment services (see IBuildEngine on MSDN) normally provided by MSBuild.

Well, the whole purpose of this experiment was to run outside of MSBuild, so we’ll roll our own “build engine” instead:

This was the piece we were missing to be able to run the transformation outside MSBuild. We are, thus, able to write our coveted test:

Our transformation runs successfully, yielding the following console output from our “build engine”:

Yay? Yay.

Conclusion

We have shown how Web.config Transformations can be invoked programmatically, outside the MSBuild process by providing a simple custom IBuildEngine realization; a technique that might prove useful for other scenarios as well.

We have applied that knowledge to enable automated testing of our transformations.

In my next post in this series, we’ll continue our investigation, aiming to get rid of the whole, nasty detour around the file system.

4 Comments

  1. Joseph Daigle

    About a year ago I built something very similar, but bypasses the “TransformXml” task and uses the transformation library from deep inside Microsoft.Web.Publishing.Tasks.dll.

    https://github.com/jdaigle/TransformXml

    This particular implementation is a console app, but the code is simple enough. And since it buffers the source file, you can actually override the source file with the transformed file result.

  2. Mads Troest

    @Joseph Thanks for sharing. Looking at your code, I can see it’s the same approach as the stuff I reverse engineered for the upcoming post. So – people eager to skip the file system detour before I post the follow-up article, have a look at https://github.com/jdaigle/TransformXml for an early spoiler. ;)

  3. Thank you for this article. It saved me a number of hours!

Trackbacks for this post

  1. The Morning Brew - Chris Alcock » The Morning Brew #999

Leave a Reply