SlowCheetah - Web.config Transformation Syntax now generalized for any XML configuration file
I did a post last year called If You're Using XCopy, You're Doing It Wrong that also included a video of my talk at Mix10 where I show how to deploy website with Web Deploy. One of the cooler not-very-well-known features is called Web.config Transformation. Once folks see it, then immediately want a general solution.
First, from the previous post:
You can right-click on your web.config and click "Add Config Transforms." When you do this, you'll get a web.debug.config and a web.release.config. You can make a web.whatever.config if you like, as long as the name lines up with a configuration profile. These files are just the changes you want made, not a complete copy of your web.config.
You might think you'd want to use XSLT to transform a web.config, but while they feels intuitively right it's actually very verbose.
Here's two transforms, one using XSLT and the same one using the XML Document Transform syntax/namespace. As with all things there's multiple ways in XSLT to do this, but you get the general idea. XSLT is a generalized tree transformation language, while this deployment one is optimized for a specific subset of common scenarios. But, the cool part is that each XDT transform is a .NET plugin, so you can make your own.
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/configuration/appSettings">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
<xsl:element name="add">
<xsl:attribute name="key">NewSetting</xsl:attribute>
<xsl:attribute name="value">New Setting Value</xsl:attribute>
</xsl:element>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Or the same thing via the deployment transform:
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add name="NewSetting" value="New Setting Value" xdt:Transform="Insert"/>
</appSettings>
</configuration>
This kind of config file transformation is so useful in fact, that it's one of the #1 feature requests...as a generalized solution. Folks want to transform their app.configs, or any XML file as part of their builds. Additionally, the current system only runs the transforms as a part of the publish process and folks would rather the transform happen "on F5" or on build. So, my team members Sayed Ibrahim Hashimi and Chuck England have done just that as a small VSiX called SlowCheetah XML Transforms.
In this screenshot I've created a simple Console Application, made a basic app.config, then right clicked and selected "Add Transform." This gives an app.debug.config and app.release.config. You could make and name these however you like, for example app.testing.config, etc.
For example, here's a basic app.config for my Console app:
<?xml version="1.0" encoding="utf-8" ?>
<configuration >
<appSettings>
<add key="appName" value="Something"/>
<add key="url" value="http://hanselman.com/"/>
<add key="email" value="awesome@hanselman.com" />
</appSettings>
</configuration>
And here's the transform to change my one value in my development time config to a debug value (or test, staging, etc). You can do this for connectionStrings, app keys, compiler settings, anything in an XML or config file.
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="appName" value="Demo-debug" xdt:Transform="Replace" xdt:Locator="Match(key)"/>
<add key="email" value="debug@contoso.com" xdt:Transform="Replace" xdt:Locator="Match(key)"/>
</appSettings>
</configuration>
Note the Transform="Replace?" That can be replace, or insert, etc. Lots of choices. The ShowCheetah XML Transform Add-In also adds a "Preview Transform" right-click menu which makes writing these way easier.
The clever part is that there's no magic. All the functionality is installed to %LOCALAPPDATA%\Microsoft\MSBuild\SlowCheetah\v1\ and lives in a standard MSBuild .targets file. You don't even need the plugin if you are installing this on a build server, just copy the files or even check them in with your source and refer to them in your project or MSBuild files. It's added in your project file for you via the tooling like any custom targets:
<Import Project="$(LOCALAPPDATA)\Microsoft\MSBuild\SlowCheetah\v1\Microsoft.Transforms.targets" Condition="Exists('$(LOCALAPPDATA)\Microsoft\MSBuild\SlowCheetah\v1\Microsoft.Transforms.targets')" />
Between the build targets and the added menu items, you get:
- Added tooling to desktop project to create XDT transforms
- Ability to transform
- app.config for desktop projects based on build configuration
- any XML file to the output folder based on build configuration
- Added tooling to enable previewing XDT transforms
- For web projects you can easily transform other XML files during package/publish
Let Sayed and the team know what you think, if it's useful, and if you use it at Sayed's blog, or the SlowCheetah project site on the VS Gallery. Enjoy!
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Thanks! :-)
How would one then write a transform for something like this?
<applicationSettings>
<FileListnerTester.Properties.Settings>
<setting name="ListenPath" serializeAs="String">
<value>c:\temp\listner\</value>
</setting>
</FileListnerTester.Properties.Settings>
</applicationSettings>
And i wanted to change the <value> for <setting name="ListenPath" ?
So for your example (my changes emphasised):
<applicationSettings>
<FileListnerTester.Properties.Settings>
<setting name="ListenPath" xdt:Locator="Match(name)">
<value xdt:Transform="Replace">c:\temp\listner\</value>
</setting>
</FileListnerTester.Properties.Settings>
</applicationSettings>
This says (if I remembered my transform syntax right!) Look inside applicationSettings & FileListnerTester.Properties.Settings to find a setting tag matched on the name attribute. Inside that replace the value tag.
When adding this kind of feature in VS, there should be no limitation like this. It is a great feature that should be available at least to all .config files by default in VS.
Most enterprise developers would want 1 package with 3 deployment options (qa, stage, prod for example), not the ability to create 3 separate packages based on my visual studio solution configuration.
MSDeploy is a wonderful tool, but seems to me the web.config transform that works pretty well in visual studio is at best a nice tool for developers, but does little to ease deployment scenarios.
It really undermines the argument.
Maybe it's related to the triviality of the examples given. It'd be interesting to have a StackOverflow Config Transform Build-Golf Contest with more realistic, comprehensive tasks, that pits XDT against XSLT.
Also, maybe the XSLT wouldn't be as verbose if Microsoft would finally implement XSLT 2.0/XPath 2.0.
The suggested solution works and the transform below also works:
<applicationSettings xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<FileListnerTester.Properties.Settings>
<setting name="ListenPath" serializeAs="String">
<value xdt:Transform="Replace">c:\temp\listner222\</value>
</setting>
</FileListnerTester.Properties.Settings>
</applicationSettings>
Only a couple drawbacks: T4 markup in the XML confuses the text editor in Visual Studio; and warnings during text template transformations cause the build to fail on the build server (don't ask me why).
If you can help me with a better XSLT example (or a 1.0 and 2.0 one) I will update the post!
XDT aims to create a new syntax to optimize for the simple add, insert and replace cases of transformations. I haven't seen enough competition between the advocates of both technologies on real-world config changes to persuade me how well it has succeeded yet.
Maybe it's just me, though. The new syntax is probably not a prohibitive barrier to entry, and it's may be very rare to require config file changes significant enough that XDT cannot produce the desired result as effectively as XSLT.
The optimizations I was thinking of, just offhand:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="@*|node()">
<xsl:copy-of select="@*|node()"/>
</xsl:template>
<xsl:template match="/configuration/appSettings">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
<add key="NewSetting" value="New Setting Value"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Unfortunately everyone's just been trained to throw whatever they need in the config file and hack together manual deployments.
Next, I copy the connection strings to my base web.config (debug does not have any transforms!) and select the Debug configuration then hit F5 it runs just fine.
What am I missing? Only Release and Debug seem to work as you said.
One alternative we tried at work was to use a web.master.config file and just use {replacementTags} and then a companion web.values.ini file using a common [configProfileSectionName] and Name=value pairs. With that and tiny, smart little search and replace console app called in a post-build action, we've had far fewer configuration errors with multiple dev, QA, UAT and production environments. Each environment gets it's own [section] and Name=value pairs. Those go into source control and allow changes to be tracked, blamed and otherwise better managed.
So far it's worked well.
Folks want to transform their app.configs, or any XML file as part of their builds. Additionally, the current system only runs the transforms as a part of the publish process and folks would rather the transform happen "on F5" or on build. So, my team members Sayed Ibrahim Hashimi and Chuck England have done just that as a small VSiX called SlowCheetah XML Transforms.
Ha! I was just looking for that this morning! It will make it much easier to commit a project to github without the sensible information of the connectionstring for example.
1. Add the connectionstring to the Web.debug.config
2. Add Web.debug.config to the .gitignore
3. ...
4. Profit
Thanks Scott :)
See details about a solution here :
http://sedodream.com/CommentView,guid,68b7e248-b9f5-4d07-bdfe-eb037bcf2cbb.aspx#commentstart
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:mode on-no-match="copy"/>
<xsl:template match="configuration/appSettings">
<xsl:apply-templates/>
<add key="NewSetting" value="New Setting Value"/>
</xsl:template>
</xsl:stylesheet>
XSLT 3.0 isn't a W3C recommendation yet, but this works fine in the latest version of Saxon.NET with XsltLanguageVersion set to "3"
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
<xsl:if test="parent::appSettings/parent::configuration">
<!-- add new settings here-->
<add key="NewSetting" value="New Setting Value"/>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
But you don't typically write an entire c# program from scratch in a single module, and likewise for xslt. If your going to do this sort of stuff more than once, and do more than just add keys, you can factor out the common stuff into a "library" as you would in a c# program. XSLT is happy to be modular and even do inheritance like c#. Here an example just to show this sort of thing can be done.
This is the "library" module, modConfig.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:mods ="config:mods">
<xsl:output indent="yes"/>
<xsl:variable name ="mods:mods"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/configuration/appSettings/add">
<xsl:if test="not($mods:mods/mods:remove/*/@name != @name)">
<!-- this is a remove, skip it-->
</xsl:if>
<xsl:if test="not($mods:mods/xsl:stylesheet/mods:replace/*/@name != @name)">
<!-- this is a replace, we will add it later-->
</xsl:if>
</xsl:template>
<xsl:template match="/configuration/appSettings">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
<xsl:copy-of select="$mods:mods/mods:add/add"/>
<xsl:copy-of select="$mods:mods/mods:replace-or-add/add"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
And this is and example file that would define some modifications you would want to make to a config file.
mymods.xsl
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:mods ="config:mods">
<xsl:import href="modConfig.xsl"/>
<xsl:variable name="mods:mods" select ="document('')/xsl:stylesheet"/>
<mods:add>
<!-- add new app settings here-->
<add key="NewSetting" value="New Setting Value" />
</mods:add>
<mods:replace-or-add>
<!-- add replace or add settings here-->
<add key="NewSetting2" value="New Setting Value2" />
</mods:replace-or-add>
<mods:remove>
<!-- add remove settings here-->
<add key="NewSetting3" />
</mods:remove>
</xsl:stylesheet>
It's got a little boiler plate, but there are parts for adds, replacements, and removes.
and here is an example file to apply the transform to:
app.config
<configuration>
<appSettings>
<add key="test" value ="7"/>
<add key="NewSetting3" value ="8"/>
</appSettings>
</configuration>
<configuration>
<appSettings>
<add key="NewSetting" value="New Setting Value" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:mods="config:mods" />
<add key="NewSetting2" value="New Setting Value2" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:mods="config:mods"/>
</appSettings>
</configuration>
Of course this is just an off the top of the head example. modConfig.xsl is a bit involved, but you write that once and then reuse it, as you would any library. With a better understanding of the req's mymods.xsl file could be simplified more or enhanced for other req's. But by using XSLT you don't really need anything "new".
I looked at the ClickOnce issue and I think I might have something that works. Can you download the file contents from http://pastebin.com/hjhcgHsB and update the file C:\Users\{YOURUSERNAME}\AppData\Local\Microsoft\MSBuild\SlowCheetah\v1\SlowCheetah.Transforms.targets Then make sure to close all your instances of VS. If it works I'll integrate these changes into the plug in.
I have started using parametrization instead which allows me to have a parameters.xml per project, and a setParameters.xml for each environment. This way I can just pass the setParameters.xml which holds configurations for the target environment when I deploy my application via msdeploy, enabling me to use the same deployment package.
I would love to see a blog post on msdeploy and parametrization Scott, since quality info on the subject seems kindda rare.
Thanks
Linking to http://msdn.microsoft.com/en-us/library/dd465326.aspx in the body of the article might be useful.
Does anyone know if there's a MSBuild equivalent to NAnt's Filter Chains for creating template files and filling the template with values from properties files (one property file per tier: dev, test, & prod)?
I think a templating approach would have a shorter learning curve. Most folks are going to be more familiar with a templating approach than with XSL or XDT (especially if they don't have a web background). After all, isn't ASP.NET just one big templating engine anyway? I haven't confirmed it, but my suspicion is that a templating (vs transforming) approach would be less verbose as well.
P.S. Typo: "The ShowCheetah XML Transform Add-In…" should be "The SlowCheetah…"
Error 11 The imported project "<Profile Directory>\Local Settings\Application Data\Microsoft\MSBuild\SlowCheetah\v1\SlowCheetah.Transforms.targets" was not found. Confirm that the path in the <Import> declaration is correct, and that the file exists on disk…
Adding a system environment variable named LOCALAPPDATA with the value "%USERPROFILE%\Application Data" fixed it. This comes from http://visualstudiogallery.msdn.microsoft.com/69023d00-a4f9-4a34-a6cd-7e854ba318b5/view/Discussions under the Q&A tab.
Thanks,
Sayed Ibrahim Hashimi
What your test setup requires should be in your App.config. Put prod settings in App.Release.config.
1. From a machine which has SlowCheetah installed copy the files from %LOCALAPPDATA%\Microsoft\MSBuild\SlowCheetah\v1\
2. Check the files into TFS
3. In your build definition specify the property SlowCheetahTargets to be the path to the SlowCheetah.Transforms.targets file
That should be it.
Sayed Ibrahim Hashimi
*******************************************************
TransformAllFiles:
Transforming Source File: C:\Builds\1\Training\TestWindowsApp_TFSDemo\Sources\TestWindowsApp_TFSDemo\TestWindowsApp_TFSDemo\app.config
Applying Transform File: app.UAT.config
Output File: C:\Builds\1\Training\TestWindowsApp_TFSDemo\Binaries\TestWindowsApp_TFSDemo.exe.config
Transformation succeeded
_CopyFilesToPublishFolder:
Creating directory "\\tfs1\Deployments\Publish\TestWindowsApp_TFSDemo\Application Files\TestWindowsApp_TFSDemo_1_0_0_18".
Copying file from "C:\Builds\1\Training\TestWindowsApp_TFSDemo\Binaries\TestWindowsApp_TFSDemo.exe.manifest" to "\\tfs1\Deployments\Publish\TestWindowsApp_TFSDemo\Application Files\TestWindowsApp_TFSDemo_1_0_0_18\TestWindowsApp_TFSDemo.exe.manifest".
Copying file from "obj\UAT\TestWindowsApp_TFSDemo.exe" to "\\tfs1\Deployments\Publish\TestWindowsApp_TFSDemo\Application Files\TestWindowsApp_TFSDemo_1_0_0_18\TestWindowsApp_TFSDemo.exe".
Creating directory "\\tfs1\Deployments\Publish\TestWindowsApp_TFSDemo\Application Files\TestWindowsApp_TFSDemo_1_0_0_18\Assemblies".
Copying file from "Assemblies\ClassDemo.dll" to "\\tfs1\Deployments\Publish\TestWindowsApp_TFSDemo\Application Files\TestWindowsApp_TFSDemo_1_0_0_18\Assemblies\ClassDemo.dll".
Creating directory "\\tfs1\Deployments\Publish\TestWindowsApp_TFSDemo\Application Files\TestWindowsApp_TFSDemo_1_0_0_18\SlowCheetah".
Copying file from "SlowCheetah\Install-Manifest.xml" to "\\tfs1\Deployments\Publish\TestWindowsApp_TFSDemo\Application Files\TestWindowsApp_TFSDemo_1_0_0_18\SlowCheetah\Install-Manifest.xml".
Copying file from "SlowCheetah\SlowCheetah.Tasks.dll" to "\\tfs1\Deployments\Publish\TestWindowsApp_TFSDemo\Application Files\TestWindowsApp_TFSDemo_1_0_0_18\SlowCheetah\SlowCheetah.Tasks.dll".
Copying file from "SlowCheetah\SlowCheetah.Transforms.targets" to "\\tfs1\Deployments\Publish\TestWindowsApp_TFSDemo\Application Files\TestWindowsApp_TFSDemo_1_0_0_18\SlowCheetah\SlowCheetah.Transforms.targets".
Copying file from "app.config" to "\\tfs1\Deployments\Publish\TestWindowsApp_TFSDemo\Application Files\TestWindowsApp_TFSDemo_1_0_0_18\TestWindowsApp_TFSDemo.exe.config".
Copying file from "C:\Builds\1\Training\TestWindowsApp_TFSDemo\Sources\TestWindowsApp_TFSDemo\TestWindowsApp_TFSDemo\Assemblies\ClassDemo.dll" to "\\tfs1\Deployments\Publish\TestWindowsApp_TFSDemo\Application Files\TestWindowsApp_TFSDemo_1_0_0_18\ClassDemo.dll".
Done Building Project "C:\Builds\1\Training\TestWindowsApp_TFSDemo\Sources\TestWindowsApp_TFSDemo\TestWindowsApp_TFSDemo\TestWindowsApp_TFSDemo.vbproj" (Publish target(s)).
Project "C:\Builds\1\Training\TestWindowsApp_TFSDemo\Sources\TestWindowsApp_TFSDemo\TestWindowsApp_TFSDemo.sln" (1) is building "C:\Builds\1\Training\TestWindowsApp_TFSDemo\Sources\TestWindowsApp_TFSDemo\ClassDemo\ClassDemo.vbproj" (3) on node 1 (Publish target(s)).
_DeploymentUnpublishable:
Skipping unpublishable project.
Done Building Project "C:\Builds\1\Training\TestWindowsApp_TFSDemo\Sources\TestWindowsApp_TFSDemo\ClassDemo\ClassDemo.vbproj" (Publish target(s)).
Done Building Project "C:\Builds\1\Training\TestWindowsApp_TFSDemo\Sources\TestWindowsApp_TFSDemo\TestWindowsApp_TFSDemo.sln" (Publish target(s)).
Build succeeded.
http://pastebin.com/hjhcgHsB#
Thank you so much for your quick answer. Maybe this is a basic question, but how I can reference SlowCheetah.Transforms.targets? I know that property is inside of the project file, but every time that I build it creates a new version (1,2,3) so the file has different location. Also, when I use the build in team foundation all the files end with deploy extension, so I do not know how to add the path. Do you have a basic hello world kind of thing as example. Thanks again for your anwers.
Just check in the files and then specify the path relative to the project directory. For example something like: "..\..\..\..\SlowCheetahIsAwesome\"
I record all my steps in 3 rough videos. These videos are aroun 20 Mb each. All the steps I did make it work. Of course chances are that I am doing this the wrong way.
Thanks agains.
PD: It you want to see these videos let me know at @richvaldivieso.
Seems like needless pain to me. Where's the love here?
EG: web.Debug.config , web.Release.config etc.
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform" xdt:Transform="Replace">
<appSettings>
<add key="appName" value="WPF Demo-Debug" />
<add key="url" value="http://localhost:8080/"/>
<add key="email" value="debug@contoso.com"/>
</appSettings>
<system.web>
<anonymousIdentification cookieless ="AutoDetect"/>
</system.web>
<connectionStrings>
<add name="RecordsDb" connectionString=".\SQLExpress;Initial Catalog=RecordsDb;Integrated Security=true"/>
</connectionStrings>
</configuration>
How can we add the xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0" attribute to the <configuration> element in a xml transformation?
original: <configuration>
target: <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
Anybody an idea?
<UsingTask
TaskName="TransformXml"
AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
...
<TransformXml
Source="$(SourceDirectory)\Example\App.config"
Transform="$(ConfigDirectory)\$(SolutionName).Example\App.$(DeploymentEnvironment).config"
Destination="$(PredeployDirectory)\$(SolutionName).Example\$(SolutionName).Example.exe.config" />
What about a transformation for all .config files in an VS solution with severals projects within ?
Thanks
It would have been better to have a web.config.template and a generated web.config in the solution.
Can anyone help? My initial searches on the internet have produced no results.
I have placed this item on the backlog. I will try and get this in for the next version.
Thanks,
Sayed Ibrahim Hashimi
<configuration>
<system.web>
<customErrors mode="Off"/>
</system.web>
</configuration>
<!-- Web.Config Configuration File -->
<configuration>
<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="mycustompage.htm"/>
</system.web>
</configuration>
I have XMLs in database that need to transform updating values, simple changes, depending on certain conditions and show to the customer.
XDT seems ideal, simpler than XSLT but how can I use it inside my C# project?
Thanks
Let's say I've got project Inner and two projects which are referencing it Outer1 and Outer2. Inner contains the xml file I want to tweak according to configuration. I also want both deployment directories to contain that xml file. Pretty standard, right? Only it doesn't work. Turns out that the only output directory in which the xml file is tweaked is Inner's output directory. Both Outer projects have the original xml file in their output directories.
I could of course change Inner's output directory to one of the Output directories but then the other one would still have the original file.
Has anyone encountered this problem before?
This is great; however I must ask whether it is limited to two configurations of the base config... i.e. Debug & Release? Let's say that a particular component gets deployed in a multiple of environments and that the component to function in those environments would require different varying environmental settings. Can this be achieved using transformations? It would seem like it should be achievable with this mechanism..
Comments are closed.
<add key="{NewSetting}" value="{New Setting Value}"/>