06 December 2010 MSBuild, Transformations, Web.config CI Team

image With Asp.NET 4.0 a new feature named "Web.config Transformations" was released. During my latest MSBuild Posts I showed you how to for example build a solution with pure MSBuild. Usually the web.config isn´t triggered and the WebApp works in debug Mod (worst case!). Because of this we need to trigger it manual in MSBuild.

 

 

[Update] With Visual Studio 2012 many problems are gone…

I wrote this blogpost using VS2010 and at that time there was one strange behaviour: All files used by the TransformXml Tasks were “locked”. So you couldn´t just override the original web.config with the transformed one. My workaround was to do some copy work but with VS2012 it should work “as expected”.

How the output looks like till now

Take a look on this post to find out how to create a _PublishedWebsites folder where the WebApp. could be founded. But here you will find the Transformation-files too:

 

image

The main problem: The transformation of the mainly web.config doesn´t happen - the application runs with debug:

    <compilation debug="true" targetFramework="4.0">

and then the call will look like this:

	<TransformXml Source="Web.config"
				  Transform="Web.Release.config"
				  Destination="Web.transformed.config" />

BUT: that sucks! Am I right?

In the end I want the transformed file with the name "web.config". the problem: TransformXml is buggy. During the build the task locked these 3 files. Anyway: We don´t want to change our web.config file in our solution. Because of this we need to create a copy of our sources and change them.

Scheme:

image

CopySource:

We copy our whole sources to another place for example one folder back in our own directory. I named it "ClientTemp".

image

BeforeCompile:

Here in the BeforeCompile targets, which are located in Clienttemp, I´m able to do some changes like for example changing the AssemblyVersion while opening. At this place I´m used to open the TransformXml Task as well:

	<TransformXml Source="..\MsBuildSample.WebApp\Web.config"
				  Transform="..\MsBuildSample.WebApp\Web.$(Configuration).config"
				  Destination="$(BuildDirFullName)MsBuildSample.WebApp\Web.config" />

Source&Transform are from the original branch and because of this we don´t need to take care of the lock.

Just the destination could be founded in our "cloned" folder. After this the "web.config" is transformed.

Build:

Here the mainly building process begins. The standard behavior is going to build the solution and pass the WebApp into the _PublishedWebsites OutDir.

---

Some other actions are able to follow. I´m going to delete the unused transformation files from the OutDir. (web.release.config / web.debug.config).

Result

It becomes a little bit trickier but the structure has her advantages as well. With this I´m able to work on my code during the building process (and before the compile) without changing the Source Code. It reminds me ( a little, little bit ;) of the functionality of TFS.

Complete buildscript:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Run">
<Import Project="$(MSBuildStartupDirectory)\Lib\MSBuild.Community.Tasks.Targets"/>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
   <PropertyGroup>
	<!-- After Compile: Result will be saved in OutDir -->
		<OutDir>$(MSBuildStartupDirectory)\..\..\ClientTemp\OutDir\</OutDir>

	<!-- Name of the BuildDir with the whole source code before the compile begins -->
		<BuildDirName>BuildDir</BuildDirName>

	<!-- Relativ part of the BuildDir -->
	<BuildDirRelativePart>..\..\ClientTemp\$(BuildDirName)</BuildDirRelativePart>

	<!-- Absolute part of the BuildDir-->
	<BuildDirFullName>$(MSBuildStartupDirectory)\$(BuildDirRelativePart)\</BuildDirFullName>

	<!-- Configuration -->
	<Configuration>Release</Configuration>

	<!-- Solutionproperties-->
		<SolutionProperties>
			OutDir=$(OutDir);
			Platform=Any CPU;
			Configuration=$(Configuration)
		</SolutionProperties>
   </PropertyGroup>
	<ItemGroup>
		<Solution Include="$(BuildDirFullName)MsBuildSample.sln">
			<Properties>
				$(SolutionProperties)
			</Properties>
		</Solution>
	</ItemGroup>
	<Target Name="Run">
	<Message Text="Run called." />

	<CallTarget Targets="CopyToBuildDir" />
	<CallTarget Targets="BeforeBuild" />
	<CallTarget Targets="Build" />
	<CallTarget Targets="Cleanup" />
	<CallTarget Targets="Zip" />
  </Target>
  <Target Name="CopyToBuildDir">
	<Message Text="CopyToBuildDir called." />
	<Exec Command="robocopy .. $(BuildDirRelativePart) /s /z /purge /a-:r" ContinueOnError="true" />
  </Target>
	<Target Name="BeforeBuild">
	<Message Text="BeforeBuild called." />

	<Message Text="Transform Xml" />
	<TransformXml Source="..\MsBuildSample.WebApp\Web.config"
									Transform="..\MsBuildSample.WebApp\Web.$(Configuration).config"
									Destination="$(BuildDirFullName)MsBuildSample.WebApp\Web.config" />
  </Target>

  <Target Name="Build">
	<Message Text="Build called." />
	<MSBuild Projects="@(Solution)"/>
	</Target>

	<Target Name="Cleanup">
		<Delete Files="$(OutDir)_PublishedWebsites\MsBuildSample.WebApp\Web.Release.config" />
		<Delete Files="$(OutDir)_PublishedWebsites\MsBuildSample.WebApp\Web.Debug.config" />

	</Target>
  <ItemGroup>
	<ZipFiles Include="$(OutDir)_PublishedWebsites\**\*.*" />
  </ItemGroup>
  <Target Name="Zip">
	<Zip Files="@(ZipFiles)"
			 WorkingDirectory="$(OutDir)_PublishedWebsites\"
			 ZipFileName="$(OutDir)Package.zip"/>
  </Target>
</Project>
 

Democode:

In my demosolution I work with some help variables of MSBuild - don´t be scared ;)

If you open the Samplecode:

D:\Samples\MsBuildSample ablegt findet Ihr das "BuildVerzeichnis" in D:\Samples\ClientTemp

[Download Democode]