Introduction
This is a weird post in some ways as it is new for me, but certainly VERY old for others. I imagine for years web developers have known about how to use the Web.Config XSLT transforms MSBUILD task. If you have not heard of this, quite simply it allows you to have a single Web.Config file and a number of other config files where ONLY the transformations are declared. Such that when the XSLT transforms MSBUILD task runs, it will take the source Web.config file along with a transformation .config file and produce a new .config file which you would use as part of your deployment process.
I have myself known about this for years too, I have even known about the Microsoft MSBUILD teams Slow Cheetah project which allows you to use this same technique outside of web projects. Ting is what I have always done is had a bunch of .config files (so one for XXX.LIVE.config one for XXXX.QA.config) that I would rename and deploy by some clever scripts.
I recently had to do a bit of work on a project that made use of the Web.Config XSLT transforms MSBUILD task, and I could clearly see in the MSBUILD file that this just used a MSBUILD
task. So I thought this must be easy enough to use stand alone. Turns out it is, you DO NOT really need to use Slow Cheetah at all. You just need to know where the Web.Config XSLT transforms MSBUILD
task is and how to use it.
The rest of this post will talk you through how to do this.
Suppose You Have This App.Config You Wish to Transform
We will concentrate on just a few areas here, those areas are the ones that are going to change between environments:
="1.0"="UTF-8"
<configuration>
<configSections>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
<section name="shareSettings" type="SimpleConfig.Section, SimpleConfig" />
</configSections>
<shareSettings productName="Shipping" ftpPath="D:\ShippingRoutes" />
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<extensions>
<add assembly="Gelf4NLog.Target" />
</extensions>
<targets async="true">
<target name="graylog" xsi:type="graylog"
hostip="dev-logging" hostport="12200"
Facility="CoolService">
<parameter name="exception"
layout="${exception:format=tostring}" optional="true" />
<parameter name="processname" layout="${processname}" />
<parameter name="logger" layout="${logger}" />
<parameter name="treadid" layout="${threadid}" />
</target>
<target name="file" xsi:type="File"
layout="${longdate} | ${level} |
${message}${onexception:${newline}EXCEPTION\:${exception:format=tostring,StackTrace}}"
fileName="c:/temp/CoolService-${shortdate}.log" />
</targets>
<rules>
<logger name="NHibernate.*" minlevel="Off"
writeTo="graylog" final="true" />
<logger name="NHibernate.*" minlevel="Error"
writeTo="file" final="true" />
<logger name="*" minlevel="Off" writeTo="graylog" />
<logger name="*" minlevel="trace" writeTo="file" />
</rules>
</nlog>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<diagnostics performanceCounters="All" />
<bindings>
<netTcpBinding>
<binding name="tcpBinding"
maxReceivedMessageSize="2147483647" closeTimeout="00:59:00"
openTimeout="00:59:00"
receiveTimeout="00:59:00" sendTimeout="00:59:00">
<security mode="None" />
<readerQuotas maxStringContentLength="8192"
maxArrayLength="20971520" />
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint name="coolServiceEndpoint"
address="net.tcp://localhost:63006/CoolService"
binding="netTcpBinding" bindingConfiguration="tcpBinding"
contract="Services.ICoolService" />
</client>
</system.serviceModel>
<system.diagnostics>
<sources>
<source name="System.ServiceModel"
switchValue="All" propagateActivity="true">
<listeners>
<add name="traceListener"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="c:\temp\CoolService.svclog" />
</listeners>
</source>
</sources>
</system.diagnostics>
</configuration>
- Custom config section (NOTE: I am using
SimpleConfig
to do that, which is awesome) - NLog logging settings
- WCF client section
- Diagnostics WCF section
So Now, Show Me The Transformations
Now this post will not (as is not meant to) teach you all about the Web.Config XSLT transforms MSBUILD
task, but rather shall show you an example. So on with the example, suppose we want to create a LIVE config file where we change the following:
- Custom config section (NOTE: I am using
SimpleConfig
to do that, which is awesome) (CHANGE ATTRIBUTES) - NLog logging settings (CHANGE Logger/Target)
- WCF client section (CHANGE ADDRESS)
- Diagnostics WCF section (REMOVE IT)
Here is how we could do that (say it's called “CoolService.LIVE.config”) :
="1.0"="UTF-8"
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<shareSettings xdt:Transform="SetAttributes" xdt:Locator="Match(productName)"
productName="Shipping" ftpPath="\\shipping\ShippingRoutes" />
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target xdt:Transform="SetAttributes"
xdt:Locator="Match(name)" name="graylog"
hostip="app-logging" />
<target xdt:Transform="SetAttributes"
xdt:Locator="Match(name)" name="file"
fileName="D:/logs/CoolService-${shortdate}.log" />
</targets>
<rules>
<logger xdt:Transform="SetAttributes"
xdt:Locator="Match(writeTo)" minlevel="trace"
writeTo="graylog" />
</rules>
</nlog>
<system.serviceModel>
<client>
<endpoint xdt:Transform="SetAttributes"
xdt:Locator="Match(name)" name="coolServiceEndpoint"
address="net.tcp://appCoolService:63006/CoolService" />
</client>
</system.serviceModel>
<system.diagnostics xdt:Transform="Remove" />
</configuration>
So How Do We Apply These Transforms
To actually apply these transforms, we can easily craft a simple MSBUILD
project file, such as (say it's called “Transforms.proj”):
="1.0"="UTF-8"
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"
DefaultTargets="Release">
<UsingTask TaskName="TransformXml"
AssemblyFile="
$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v12.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
<ItemGroup>
<Config Include="LIVE">
<Environment>LIVE</Environment>
</Config>
<Config Include="QA">
<Environment>QA</Environment>
</Config>
</ItemGroup>
<Target Name="Release">
<MakeDir Directories="CoolService\Configuration\%(Config.Environment)" />
<TransformXml Source="App.config" Transform="CoolService.%(Config.Identity).config"
Destination="CoolService\Configuration\%(Config.Environment)\CoolService.exe.config" />
</Target>
</Project>
Where the $(MsBuildExtensionsPath)
will likely be something like “C:\Program Files (x86)\MSBuild\”. So once we have a MSBUILD file like this in place, it is just a simple matter of running MSBUILD
something like:
MSBUILD Transforms.proj
Which will result in the following being produced:
="1.0"="UTF-8"
<configuration>
<configSections>
<section name="nlog"
type="NLog.Config.ConfigSectionHandler, NLog" />
<section name="shareSettings"
type="SimpleConfig.Section, SimpleConfig" />
</configSections>
<shareSettings productName="Shipping"
ftpPath="\\shipping\ShippingRoutes" />
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<extensions>
<add assembly="Gelf4NLog.Target" />
</extensions>
<targets async="true">
<target name="graylog" xsi:type="graylog"
hostip="app-logging" hostport="12200"
Facility="CoolService">
<parameter name="exception"
layout="${exception:format=tostring}" optional="true" />
<parameter name="processname" layout="${processname}" />
<parameter name="logger" layout="${logger}" />
<parameter name="treadid" layout="${threadid}" />
</target>
<target name="file" xsi:type="File"
layout="${longdate} | ${level} |
${message}${onexception:${newline}EXCEPTION\:${exception:format=tostring,StackTrace}}"
fileName="D:/logs/CoolService-${shortdate}.log" />
</targets>
<rules>
<logger name="NHibernate.*" minlevel="trace"
writeTo="graylog" final="true" />
<logger name="NHibernate.*" minlevel="Error"
writeTo="file" final="true" />
<logger name="*" minlevel="trace" writeTo="graylog" />
<logger name="*" minlevel="trace" writeTo="file" />
</rules>
</nlog>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<diagnostics performanceCounters="All" />
<bindings>
<netTcpBinding>
<binding name="tcpBinding"
maxReceivedMessageSize="2147483647" closeTimeout="00:59:00"
openTimeout="00:59:00"
receiveTimeout="00:59:00" sendTimeout="00:59:00">
<security mode="None" />
<readerQuotas maxStringContentLength="8192"
maxArrayLength="20971520" />
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint name="coolServiceEndpoint"
address="net.tcp://appCoolService:63006/CoolService"
binding="netTcpBinding" bindingConfiguration="tcpBinding"
contract="Services.ICoolService" />
</client>
</system.serviceModel>
</configuration>