Thu Nov 06 2014

Transforming web.config values with Rake

I've really been enjoying using Albacore Rake instead of MSBuild at work. It's enabled us to get everyone involved (because ugh, msbuild xml) and to improve our CI/CD pipeline.

Today we were talking about reducing the number of build configurations we have… which we only have in order to support config transforms.

NB: this is all with Albacore Rake version one.

Here's a rakefile that will clean and build a .Net solution

require 'albacore'

SOLUTION_FILE = "../SolutionName.sln"
BUILD_CONFIG = ENV['Configuration'] || "Release"

MSBUILD_PROPERTIES = {
  :configuration => BUILD_CONFIG,
  :VisualStudioVersion => 12.0 # or you have to edit csproj for this to work
}

task :default => [:build]

msbuild :clean do |msb|
    msb.properties = MSBUILD_PROPERTIES
    msb.targets :Clean
    msb.solution = SOLUTION_FILE
    msb.verbosity = :normal
end

msbuild :build=>[:clean] do |msb|
    msb.properties = MSBUILD_PROPERTIES
    msb.targets :Build
    msb.solution = SOLUTION_FILE
    msb.verbosity = :normal
end

In this scenario I want to replace the value of an app setting key. The section in the web.config in question:

  <appSettings>
    <add key="TheSetting" value="value that needs to change"/>
  </appSettings>

aside: don't always do this!

The web.config transform to replace one or two values is pretty straightforward so if you don't have many different configurations or many things to change then you can probably stick with that.

But if you want to be able to apply the transform outside of packaging/deploying or if things are getting gnarly then I definitely recommend exploring albacore

A template web.config

I keep rake files in a folder at the root of the solution named Build (but they don't have to be there). And so I added a folder named templates to that. Then added a copy of the web.config to that folder.

This annoys and worries me as it will need to be kept in sync as the web.config changes (say someone adds a Nuget package to the project that alters the web.config). And so this feels like a potential source of error. But…

In this file any value that needs changing can be replaced with a ruby string interpolation format placeholder thingy (hmmm, did I give away that I don't ruby programming language much?)

So…

  <appSettings>
    <add key="TheSetting" value="%{the_setting}"/>
  </appSettings>

YAML

YAML or YAML Ain't Markup Language is a "human friendly data serialization standard for all programming languages.". There's more to learn about yaml in ruby here

For each build config add a yaml file. In this example case I added a release.yaml file to the Build/templates folder:

:the_setting: altered-value

Complex, right? Wrong! If anything there's too little text in here for my tastes (although I'm unfamiliar with yaml so it may be because of the effort I have to expend to parse it)

The important thing here is that the yaml key begins with a colon to support the string replacement method used below.

The rake task

The rakefile should look like this:

require 'albacore'
require 'yaml'

SOLUTION_FILE = "../SolutionName.sln"
BUILD_CONFIG = ENV['Configuration'] || "Release"
WEBCONFIG_PATH = "../ProjectName/Web.config"

MSBUILD_PROPERTIES = {
  :configuration => BUILD_CONFIG,
  :VisualStudioVersion => 12.0
}

task :default => [:build]

msbuild :clean do |msb|
    msb.properties = MSBUILD_PROPERTIES
    msb.targets :Clean
    msb.solution = SOLUTION_FILE
    msb.verbosity = :normal
end

msbuild :build=>[:clean] do |msb|
    msb.properties = MSBUILD_PROPERTIES
    msb.targets :Build
    msb.solution = SOLUTION_FILE
    msb.verbosity = :normal
end

task :transform_config do 
  configHash = YAML.load_file("templates/#{BUILD_CONFIG}.yaml")
  templateConfig = File.read("templates/web.config") 
  newConfig = templateConfig % configHash
  File.write(WEBCONFIG_PATH, newConfig)
end

This file has a new task which loads the settings from the yaml file into a hash. And then loads the contents of the web.config template as a string.

It then uses the string % hash method of string interpolation that has been available since Ruby 1.9.2

This mechanism requires that the hash keys are symbols and not strings which is why the keys in the yaml file have to begin with a colon.

Does this solve my problems?

More config yaml files can be added. Whole sections can be excluded from configs by being replaced with empty strings. And more importantly the project and solution files don't need to know about these configurations to support different values in different deployments.

There might be pain points here I haven't discovered in this example but I like what I see so far!

More like this...