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.
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:
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!
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.
<applicationSettings> <FileListnerTester.Properties.Settings> <setting name="ListenPath" serializeAs="String"> <value>c:\temp\listner\</value> </setting> </FileListnerTester.Properties.Settings></applicationSettings>
<applicationSettings> <FileListnerTester.Properties.Settings> <setting name="ListenPath" xdt:Locator="Match(name)"> <value xdt:Transform="Replace">c:\temp\listner\</value> </setting> </FileListnerTester.Properties.Settings></applicationSettings>
<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>
<?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>
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.
<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>
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…
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.
<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>
<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" />
Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.