C#

App.Config Transforms Outside Of Web Project

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 developer 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 to, 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 area are the ones that are going to change between environments:

 

<?xml version="1.0" encoding="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">
  </shareSettings>
 
  <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>
      <!-- CoolService -->
      <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 its called “CoolService.LIVE.config”) :

 

<?xml version="1.0"?>
<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:xsi="http://www.w3.org/2001/XMLSchema-instance" 		
        xmlns="http://www.nlog-project.org/schemas/NLog.xsd">
      <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 its called “Transforms.proj”):

 

<Project ToolsVersion="4.0" 
	DefaultTargets="Release" 
	xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <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:

 

<?xml version="1.0" encoding="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">
  </shareSettings>
 
  <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>
      <!-- CoolService -->
      <endpoint name="coolServiceEndpoint" 
	        address="net.tcp://appCoolService:63006/CoolService" 
		binding="netTcpBinding"
                bindingConfiguration="tcpBinding" 
                contract="Services.ICoolService" />
    </client>
  </system.serviceModel>
 
 
</configuration>

7 thoughts on “App.Config Transforms Outside Of Web Project

  1. Thanks for your article Sacha.

    I’ve been using the TransformXml task in non-web projects for years, but I didn’t know about the trick to create Config elements to declare the transformations… I was just adding one TransformXml task for each possible configuration. I’ll certainly do it your way next time!

    1. Thomas,

      Would like to know what you thought of the Akka.NET article, you played with that yet?

  2. ItemGroup are your friends, arrays of the MSBUILD world. I hate MSBUILD to be honest, we use FAKE these days, that’s cool

Leave a comment