- | rssFeed | My book on MSBuild and Team Build | Archives and Categories Sunday, August 19, 2012

Profile specific web.config transforms and transform preview

When we released VS2010 we add support for web.config (XDT) transforms during publish/package. Note: From now on I’ll only use the word publish from now on but the full content relates to packaging as well. In the original implementation when you published your web project the web.config file would be transformed by the file web.{Configuration}.config, where {Configuration} is the project build configuration. For example Debug, or Release. If you publish on Release and there exists a web.release.config we will take your web.config and transform it with web.release.config before publishing.

Cascading web.config transformations

In VS 2012 (as well as the publishing updates for VS2010 through the Azure SDK) now support the concept of publish specific transforms. You can also now specify the project configuration used for a profile when publishing on the publish dialog.

SNAGHTML45b19fc

In this case I have created a profile named Production and set the Configuration to Release. When I publish this project the following transformations will be applied (if the files exist) in this order.

  1. web.release.config
  2. web.production.config

I think that we got this wrong when we initially implemented the support. We should have created profile specific transforms instead of ones based on build config, but having these cascading transforms are still pretty useful. For example I may want to remove the attribute debug=”true” from the compilation element and then inside of the profile specific transform we would override appSettings/WCF endpoints/logging config/etc for that environment.

In VS there is a right-click option on web.config for Add Config Transform, but we were not able to update the functionality of that to automatically create profile specific transforms. Don’t worry it will be released soon with our first set of updates for web tooling. For now you will need to create a new file with the correct name and add it to your project. Note: if you want it to show up nested under web.config you’ll need to add the metadata <DependentUpon>Web.config</DependentUpon> to the item in the .csproj/.vbproj file.

web.config transform preview

Previously the only way to test the functionality for these transformation was to actually publish or package the web project. This gets old pretty quick. In order to simplify creating these transforms we have introduced the Preview Transform menu option. This is the coolest feature in VS 2012 (OK I’m a bit biased, but still its the coolest).

image

In my web.release.config I have left the default contents, which just removes the debug attribute. Here is what I see when I select this on web.release.config for my project.

image

You can see that in the image above we can see that the debug flag was indeed removed as expected.

In my web.production.config I have a transform which simply updates the email app setting value. Here is the really cool part when I preview the transform for web.production.config the previewer will look into the profile and determine the build configuration which has been configured, and it will ensure that transform is applied before the profile specific one. For example take a look at the result for web.production.config.

image

In the image above you can see the note that web.release.config was applied first followed by web.production.config. In the result we can see that web.release.config removed the debug flag and that web.production.config updated the email address value.

We also do a little bit to help out in case there are errors in either the web.config or a transform. You can see errors in the Output Window and double click it to go directly to where the error exists.

Note: Scott Hanselman has a 5 minute video showing this and other updates.

Another note: If you need to transform any file besides web.config during publish then install my extension SlowCheetah.

Sayed Ibrahim Hashimi | @SayedIHashimi

Config-Transformation | msbuild | Visual Studio | Visual Studio 2012 Sunday, August 19, 2012 11:18:16 PM (GMT Daylight Time, UTC+01:00)  #     | 
Saturday, January 08, 2011

Video on Web Deployment using Visual Studio 2010 and MSDeploy

Back in November I participated in Virtual Tech Days which is an online conference presented by Microsoft. In the session I discussed the enhancements to web deployment using Visual Studio 2010 and MSDeploy. Some of the topics which I covered includ:

You can download the video & all of my sample files at http://virtualtechdays.com/pastevents_2010november.aspx. In the samples you will find all of the scripts that I used and a bunch of others which I didn’t have time to cover. Enjoy!

Sayed Ibrahim Hashimi @sayedihashimi

Config-Transformation | IIS | msbuild | MSDeploy | speaking | Visual Studio | Visual Studio 2010 | web | Web Deployment Tool | Web Development | Web Publishing Pipeline Saturday, January 08, 2011 8:34:08 PM (GMT Standard Time, UTC+00:00)  #     | 
Thursday, September 09, 2010

Extending XML (web.config) Config transformation

Last week on StackOverflow I answered a question, Make web.config transformations working locally and in a response to my answer the question asker asked me if I would be able to a question he posed earlier Advanced tasks using web.config transformation. Evidently he is really interested in config transformations! I don’t blame him, I’m really into them as well.

In his question he asks (summarizing) can we replace portions of attribute values instead of this entire attribute? So for instance you have the following in your web.config. Below is two sets of appSettings one from Dev and the other from Prod (taken from the original question).

<!-- DEV ENTRY -->
<appSettings>
 <add key="serviceName1_WebsService_Url" value="http://wsServiceName1.dev.domain.com/v1.2.3.4/entryPoint.asmx" />
 <add key="serviceName2_WebsService_Url" value="http://ma1-lab.lab1.domain.com/v1.2.3.4/entryPoint.asmx" />
</appSettings>

<!-- PROD ENTRY -->
<appSettings>
 <add key="serviceName1_WebsService_Url" value="http://wsServiceName1.prod.domain.com/v1.2.3.4/entryPoint.asmx" />
 <add key="serviceName2_WebsService_Url" value="http://ws.ServiceName2.domain.com/v1.2.3.4/entryPoint.asmx" />
</appSettings>

In the above we just want to replace dev with prod and ma1-lab.lab1.domain with ws.ServiceName2.domain. For those wondering currently we have the following transformations out of the box.

At the end of this article I’ve linked to another blog which has more info about these transformations. So it sounds like SetAttributes is almost what we want, but not quite what there. A little known fact is that you can create your own config transformations and use those. In fact all of the out of the box transformations follow the same patterns that custom transformations would. To solve this issue we need to create our own config transformation, AttributeRegexReplace. This transformation will take an attribute value and do a regular expression replace on its value. In order to create a new transformation you first reference the Microsoft.Web.Publishing.Tasks.dll which can be found in the %Program Files (x86)%MSBuild\Microsoft\VisualStudio\v10.0\Web folder. If you are working with a team it is best if you copy that assembly, place it in a shared folder in source control, and make the reference from that location. After you create the reference to that assembly you will need to create a class which extends the Transform class. The class diagram for this abstract class is shown below.

image

The only thing that you will need to implement is the Apply method. You don’t even need to fully understand all of the properties and methods just the portions that you are interested in. Here we will not cover all the details of this class, or other related classes which exist, but there will be future posts which will shed more light on this area.

In the sample class library that I created, I called the project CustomTransformType. Inside of that project I created the class AttributeRegexReplace. The entire contents of that class are shown below, we will go over the details after that.

namespace CustomTransformType
{
    using System;
    using System.Text.RegularExpressions;
    using System.Xml;
    using Microsoft.Web.Publishing.Tasks;

    public class AttributeRegexReplace : Transform
    {
        private string pattern;
        private string replacement;
        private string attributeName;

        protected string AttributeName
        {
            get
            {
                if (this.attributeName == null)
                {
                    this.attributeName = this.GetArgumentValue("Attribute");
                }
                return this.attributeName;
            }
        }
        protected string Pattern
        {
            get
            {
                if (this.pattern == null)
                {
                    this.pattern = this.GetArgumentValue("Pattern");
                }

                return pattern;
            }
        }

        protected string Replacement
        {
            get
            {
                if (this.replacement == null)
                {
                    this.replacement = this.GetArgumentValue("Replacement");
                }

                return replacement;
            }
        }

        protected string GetArgumentValue(string name)
        {
            // this extracts a value from the arguments provided
            if (string.IsNullOrWhiteSpace(name)) 
            { throw new ArgumentNullException("name"); }

            string result = null;
            if (this.Arguments != null && this.Arguments.Count > 0)
            {
                foreach (string arg in this.Arguments)
                {
                    if (!string.IsNullOrWhiteSpace(arg))
                    {
                        string trimmedArg = arg.Trim();
                        if (trimmedArg.ToUpperInvariant().StartsWith(name.ToUpperInvariant()))
                        {
                            int start = arg.IndexOf('\'');
                            int last = arg.LastIndexOf('\'');
                            if (start <= 0 || last <= 0 || last <= 0)
                            {
                                throw new ArgumentException("Expected two ['] characters");
                            }

                            string value = trimmedArg.Substring(start, last - start);
                            if (value != null)
                            {
                                // remove any leading or trailing '
                                value = value.Trim().TrimStart('\'').TrimStart('\'');
                            }
                            result = value;
                        }
                    }
                }
            }
            return result;
        }

        protected override void Apply()
        {
            foreach (XmlAttribute att in this.TargetNode.Attributes)
            {
                if (string.Compare(att.Name, this.AttributeName, StringComparison.InvariantCultureIgnoreCase) == 0)
                {
                    // get current value, perform the Regex
                    att.Value = Regex.Replace(att.Value, this.Pattern, this.Replacement);
                }
            }
        }
    }
}

In this class we have 3 properties; Pattern, Replacement, and AttributeName. All of these values will be provided via an argument in the config transformation. For example take a look at the element below which contains a transform attribute may look like the following.

<add key="two" value="two-replaced" 
         xdt:Transform="AttributeRegexReplace(Attribute='value', Pattern='here',Replacement='REPLACED')" 
         xdt:Locator="Match(key)"/>

In this example I declare that I am using AttributeRegexReplace and then specify the values for the attributes within the (). In the class above I have a method, GetArgumentValue, which is used to parse values from that argument string. When your transform is invoked the string inside of () is passed in as the ArgumentString value. If you are using a , as the argument separator, as I am, then you can use the Arguments list. Which will split up the arguments by the , character. Surprisingly in the 101 lines of code in the sample there are only a few interesting lines. Those are what’s contained inside the Apply method. Inside that method I search the TargetNode’s attributes (TargetNode is the node which was matched in the xml file being transformed) for an attribute with the same name as the one specified in the AttributeName property. Once I find it I just make a call to Regex.Replace to get the new value, and assign it. Pretty simple! Now lets see how can we use this.

Let’s say you have the following very simple web.config

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="one" value="one"/>
    <add key="two" value="partial-replace-here-end"/>
    <add key="three" value="three here"/>
  </appSettings>
</configuration>

If we want to be able to use our own transform then we will have to use the xdt:Import element. You can place that element inside the xml document anywhere immediately under the root element. This element will allow us to utilize our own transform class. It only has 3 possible attributes.

You can only use one of the two; path and assembly. Basically it boils down to how the assembly is loaded. If you use path the assembly will be loaded with Assembly.LoadFrom and if you chose to use assembly passing in the AssemblyName, for instance if the assembly in in the GAC, then it will be loaded using Assembly.Load.

I chose to use path, because I just placed the file inside of the MSBuild Extensions directory (%Program Files (x86)%MSBuild) in a folder named Custom. Then I created my config transform file to be the following.

<?xml version="1.0"?>

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
                        
  <xdt:Import path="C:\Program Files (x86)\MSBuild\Custom\CustomTransformType.dll"
              namespace="CustomTransformType" />

  <appSettings>
    <add key="one" value="one-replaced" xdt:Transform="Replace" xdt:Locator="Match(key)" />
    <add key="two" value="two-replaced" xdt:Transform="AttributeRegexReplace(Attribute='value', Pattern='here',Replacement='REPLACED')" xdt:Locator="Match(key)"/>
  </appSettings>
</configuration>

Then to run this I created an MSBuild file, PerformTransform.proj, which is shown below.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Demo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="TransformXml"
           AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>

  <PropertyGroup>
    <TransformDest>web.tranzed.config</TransformDest>
  </PropertyGroup>
  
  <Target Name="Demo">
    <Delete Files="$(TransformDest)" />
    <TransformXml Source="web.config"
                  Transform="web.dev.config"
                  Destination="$(TransformDest)" />                  
  </Target>
  
</Project>

This file uses the TransformXml task as I outlined in a previous post Config transformations outside of web app builds. Once you execute the Demo target with the command msbuild PerformTransform.proj /t:Demo you will see the file web.tranzed.config with the following contents.

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="one" value="one-replaced"/>
    <add key="two" value="partial-replace-REPLACED-end"/>
    <add key="three" value="three here"/>
  </appSettings>
</configuration>

So you can see that the replacement did occur as we intended. Below you will find the download link for the samples as well as another blog entry for more info on the out of the box transformations.

Resources

Sayed Ibrahim Hashimi

Config-Transformation | msbuild | MSDeploy Thursday, September 09, 2010 6:51:43 AM (GMT Daylight Time, UTC+01:00)  #     | 
Monday, April 26, 2010

Config transformations outside of web app builds

If you are using Visual Studio 2010 then you may already be familiar with the Web.config transformations that are now available. What you might not know is that you can use that same technology to transform config files outside of the build process. You will need Visual Studio 2010 installed on the machine where you perform these transformations. It is very easy to perform these transformation as well. Let’s say that we start with the app.config file shown below.

<configuration>
    <connectionStrings>
        <clear/>
        <add name="Default" connectionString="Data Source=localhost;Initial Catalog=Sample01;Integrated Security=True;" />
    </connectionStrings>
    
    <appSettings>
        <add key="contactEmail" value="contact@demo.example.com"/>
        <add key="siteUrl" value="http://demo.example.com"/>
    </appSettings>
    
</configuration>

Then we create another file, transform.xml, which contains our transformations. That file is shown below.

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <connectionStrings>
        <clear/>
        <add name="Default" connectionString="Data Source=NOT-localhost;Initial Catalog=Sample01;Integrated Security=True;" 
             xdt:Locator="Match(name)" xdt:Transform="Replace"/>
    </connectionStrings>

    <appSettings>
        <add key="contactEmail" value="contact@example.com" xdt:Locator="Match(key)" xdt:Transform="Replace"/>
        <add key="siteUrl" value="http://example.com" xdt:Locator="Match(key)" xdt:Transform="Replace"/>
    </appSettings>

</configuration>

Then we can easily execute the transformations by using MSBuild. So I created a file named trans.proj and it is shown below.

<Project ToolsVersion="4.0" DefaultTargets="Demo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <UsingTask TaskName="TransformXml"
             AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>

    <Target Name="Demo">
        <TransformXml Source="app.config"
                      Transform="Transform.xml"
                      Destination="app.prod.config"/>
    </Target>
</Project>

This MSBuild file uses the TransformXml task which is shipped with Visual Studio 2010. We specify the source file, transform file and the destination. Pretty straight forward.

In order to execute this I open a Visual Studio 2010 command prompt, browse to the directory containing both files, and enter the following command

msbuild trans.proj /t:Demo

Once you do this then you will find the file app.prod.config with the following contents.

<configuration>
    <connectionStrings>
        <clear/>
        <add name="Default" connectionString="Data Source=NOT-localhost;Initial Catalog=Sample01;Integrated Security=True;"/>
    </connectionStrings>
    
    <appSettings>
        <add key="contactEmail" value="contact@example.com"/>
        <add key="siteUrl" value="http://example.com"/>
    </appSettings>
    
</configuration>

Sayed Ibrahim Hashimi

Config-Transformation | msbuild | MSBuild 4.0 | MSDeploy | Visual Studio | Visual Studio 2010 Monday, April 26, 2010 5:22:06 AM (GMT Daylight Time, UTC+01:00)  #     |