Thursday, July 5, 2012

Transforms for *.config Files, Part 1: Transform on Build

Part 1: Transform on Build
This first installment on Transforms will focus on what exactly transforms are and what their default behavior is and how to make them more useful in your day-to-day development by making them transform during the BUILD process rather than the deployment.

Default Transforms in VS2010 – MVC3 Project Template (and other templates)
2 Default Solution Configurations: Debug & Release
2 Pre-Built Transforms (Debug & Release) + 1 Default Web.config

clip_image002clip_image004

The given Web.config file by default has debugging turned on (debug=”true”):

<system.web>
<compilation debug="true" targetFramework="4.0">
. . .
</compilation>
</system.web>

The “Web.Debug.config” transform does nothing; the “Web.Release.config” transform has this line:


<system.web>
<compilation xdt:Transform="RemoveAttributes(debug)" />
</system.web>


This says:
Find the “compilation” tag and remove the attribute called “debug”. Which means the transformed config file will look like this:


<system.web>
<compilation targetFramework="4.0">
. . .
</compilation>
</system.web>



Most Common Transform: Connection Strings

Web.config might have:



<connectionStrings>
<add name="MyDB"
connectionString="Data Source=TestSvr;Initial Catalog=MyTestDB;Integrated Security=True" />
</connectionStrings>


Web.Release.config transform might have:


<connectionStrings> 
<add name="MyDB"
connectionString="Data Source=ReleaseSvr;Initial Catalog=MyReleaseDB;Integrated Security=True"
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</connectionStrings>



Web.config AFTER transform:


<connectionStrings>
<add name="MyDB"
connectionString="Data Source=ReleaseSvr;Initial Catalog=MyReleaseDB;Integrated Security=True" />
</connectionStrings>


I. Transforms Only Performed On Publish (By Default as a step in the MSBuild Process)
   a. This means that you cannot “test” a transform unless you “test” a publish

II. What if you could make a transform happen earlier – like when you BUILD the solution?
   a. You could then TEST that a transform worked
   b. You could create your own LOCAL transforms

Making a transform happen when you BUILD a solution
Method 1: Modify the Project
RtClick >> Unload project, then RtClick >> Edit *.csproj
Scroll to commented section:



<!-- To modify your build process, add your task inside one of the targets below and
uncomment it. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target> -->


Uncommented the “BeforeBuild” command and inserted the following TransformXml command:


<!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
Other similar extension points exist, see Microsoft.Common.targets. -->
<Target Name="BeforeBuild">
<TransformXml Source="Web.config" Transform="Web.$(Configuration).config" Destination="Web.Transform.config" />
</Target>
<!--
<Target Name="AfterBuild">
</Target> -->


Save, Close, then RtClick >> Reload project & Build. “Show All Files” and you should see “Web.Transform.config” – This file will have the “debug=’true’” removed from the “compilation” tag.

To see what MSBuild is doing:
Tools >> Options >> Projects and Solutions >> Build and Run >> MSBuild project build output verbosity
Normal:
BeforeBuild:
   Transforming Source File: Web.config
   Applying Transform File: Web.Release.config
   Output File: Web.Transform.config
Transformation succeeded


Detailed:
Target "BeforeBuild" in project "c:\projects\applications\MVC3ConfigTest\MVC3ConfigTest\MVC3ConfigTest.csproj" (target "Build" depends on it):
   Task "TransformXml"
      Transforming Source File: Web.config
      Applying Transform File: Web.Release.config
      Executing RemoveAttributes (transform line 18, 18)
         on /configuration/system.web/compilation
      Applying to 'compilation' element (source line 20, 6)
      Removed 'debug' attribute
      Removed 1 attributes
      Done executing RemoveAttributes
      Output File: Web.Transform.config
      Transformation succeeded


Diagnostic:
Target "BeforeBuild: (TargetId:3)" in project "c:\projects\applications\MVC3ConfigTest\MVC3ConfigTest\MVC3ConfigTest.csproj" (target "Build" depends on it):
   Task "TransformXml" (TaskId:4)
      Transforming Source File: Web.config (TaskId:4)
      Applying Transform File: Web.Release.config (TaskId:4)
      Executing RemoveAttributes (transform line 18, 18) (TaskId:4)
         on /configuration/system.web/compilation (TaskId:4)
      Applying to 'compilation' element (source line 20, 6) (TaskId:4)
      Removed 'debug' attribute (TaskId:4)
      Removed 1 attributes (TaskId:4)
      Done executing RemoveAttributes (TaskId:4)
      Output File: Web.Transform.config (TaskId:4)
      Transformation succeeded (TaskId:4)


Need Web.Transform.config to be Web.config, Solution: Web.BASE.config
Web.Transform.config is ok for visually seeing what your transforms are doing, but to actually run your project with the transformed config file, you need the transformed file to be Web.config and in your project folder. But you do not want to overwrite you original Web.config. To accomplish this, copy & paste your Web.config file into your project and name the copied file Web.BASE.config (if you get Debug.BASE and Release.BASE files also, remove them).

To apply the transform to Web.BASE.config and have the result written to Web.config, edit the project and change the TransformXml line to read:



<Target Name="BeforeBuild"> 
<TransformXml Source="Web.BASE.config" Transform="Web.$(Configuration).config" Destination="Web.config" />
</Target>


Save, Close, Reload Project & Build (with Normal MSBuild output verbosity), result:
BeforeBuild:
   Transforming Source File: Web.BASE.config
   Applying Transform File: Web.Release.config
   Output File: Web.config
   Transformation succeeded


MSBuild used Web.BASE.config as the starting file, then applied the transforms in our currently selected configuration (Release) and wrote the results to Web.config. If we now RUN the code, it will use our newly created Web.config settings. So we can easily test the program in BOTH Debug AND Release modes (or any other configuration we might create).

Method 2: (Project).wpp.targets
WPP = Web Publish Pipeline (another name for the MSBuild process)
Revert the *.csproj file back to is original (commented status – no TransformXml line)
RtClick Project >> Add New Item >> Visual C# >> Data >> XML File
Name it: (Project).wpp.targets

In this file add the following:



<?xml version="1.0" encoding="utf-8" ?> 
<Project ToolsVersion="4.0" 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="TransformWebBASEConfig" BeforeTargets="BeforeBuild" >
<TransformXml Source="$(ProjectDir)Web.BASE.config" Transform="$(ProjectDir)Web.$(Configuration).config" Destination="$(ProjectDir)Web.config" />
</Target>
</Project>


Save, Close & Build (with Normal Verbosity) and you should see:
TransformWebBASEConfig:
   Transforming Source File:
c:\projects\applications\MVC3ConfigTest\MVC3ConfigTest\Web.BASE.config
   Applying Transform File: c:\projects\applications\MVC3ConfigTest\MVC3ConfigTest\Web.Release.config
   Output File: c:\projects\applications\MVC3ConfigTest\MVC3ConfigTest\Web.config
   Transformation succeeded


If you turn on Detailed verbosity, you can see the following relevant lines:
WebPublishPipelineCustomizeTargetFile = c:\projects\applications\MVC3ConfigTest\MVC3ConfigTest\MVC3ConfigTest.wpp.targets
. . .
Target "TransformWebBASEConfig: (TargetId:3)" in file "c:\projects\applications\MVC3ConfigTest\MVC3ConfigTest\MVC3ConfigTest.wpp.targets" from project "c:\projects\applications\MVC3ConfigTest\MVC3ConfigTest\MVC3ConfigTest.csproj" (target "BeforeBuild" depends on it):
   Task "TransformXml" (TaskId:4)
   Transforming Source File: c:\projects\applications\MVC3ConfigTest\MVC3ConfigTest\Web.BASE.config (TaskId:4)
   Applying Transform File: c:\projects\applications\MVC3ConfigTest\MVC3ConfigTest\Web.Release.config (TaskId:4)
   Executing RemoveAttributes (transform line 18, 18) (TaskId:4)
      on /configuration/system.web/compilation (TaskId:4)
   Applying to 'compilation' element (source line 20, 6) (TaskId:4)
   Removed 'debug' attribute (TaskId:4)
   Removed 1 attributes (TaskId:4)
   Done executing RemoveAttributes (TaskId:4)
   Output File: c:\projects\applications\MVC3ConfigTest\MVC3ConfigTest\Web.config (TaskId:4)
   Transformation succeeded (TaskId:4)
   Done executing task "TransformXml". (TaskId:4)
   Done building target "TransformWebBASEConfig" in project   "MVC3ConfigTest.csproj".: (TargetId:3)


Part 2 of this series will making NEW configuration profiles and incorporating them into your build process.


Transforms for ANY .config file: 5 Part Series
Part 1: Transforms on Build
This first part does an overview of the default behavior of transforms.  It then shows how you can plug into the MSBuild process and apply your transforms at Build time rather than the default publish time.

Part 2: New Config Profiles
The second part of the series shows how to create a new solution configuration and the nuances of what happens behind the scenes (in case you want to delete a configuration). It also discusses having a local development configuration specific to an individual developer.

Part 3: Other Config Files
The third part discusses how to add a configuration for another element of the application (using log4net as an example).  This post shows how this process differs from the handling of Web.config.

Part 4: Making the transforms look nice
This fourth part is just a quickie to show how you can manually make your other configuration files sit under their respective parent files.

Part 5: SlowCheetah & Advanced
This fifth and final post is where I try to bring together all of the elements from the previous posts together and introduce a Visual Studio extension that allows much of the previous posts to be accomplished easily and automatically.

No comments:

Post a Comment