hckr.fyi // thoughts

Team Foundation Server 2015 Build and Release with Web.config...

by Michael Szul on

There's a nice little bit of functionality in .NET that allows you to specify certain elements as replacement elements in the Web.config file depending on what configuration you build. Your ASP.NET web application will come with a Web.config file, but also a Web.Debug.config file and a Web.Release.config file. The secondary files can contain the same elements as the primary Web.config, but by using special transform attributes. When the application builds, it takes those attributes and runs a replacement in the primary Web.config. The most common example is with connection strings. You might have a local database that you use when developing, but in production, you use a different database. You can add that database string to the Web.Release.config file, and upon publishing, MSBuild will transform the Web.config file according to those attributes, and swap the connection strings.

You already knew all this?

Great! Now make it work in Team Foundation Server (TFS). Why do I say that? TFS builds and deployments do not run the same publishing steps, so the transformation doesn't actually occur.

How to fix this, you ask?

Well, you can try several steps obtained from the Internet to various degrees of success.

Cursory searches reveal this post on StackExchange, which instructs you to add a <Target> node to your csproj file, and then add the following MSBuild argument to your build task: /p:TransformConfigFiles=true.

Another suggestion (scroll to the bottom answer) suggests to use the following MSBuild arguments: /p:UseWPP_CopyWebApplication=true /p:PipelineDependsOnBuild=false.

Neither of these suggestions seem to work in TFS 2015. In fact, reading the original poster's message, you can see that he has scoured a great many blog posts searching for an answer, but coming up empty.

Luckily, I found an answer that actually does work. I took that answer, and then cut it down for my own purposes, resulting in the following code that needs to be added to the csproj file:

<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v14.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
      <Target Name="ApplyTransform" Condition="Exists('Web.$(Configuration).config')">
        <TransformXml Source="web.config" Transform="Web.$(Configuration).config" Destination="Web.config" />
      </Target>
      <Target Name="BeforeBuild">
        <Exec Command="attrib -r Web.config" />
        <CallTarget Targets="ApplyTransform" />
      </Target>
    

The first line essentially imports the TransformXml method from Microsoft.Web.Publishing.Tasks.dll. Note that the path points to a path in the MSBuild directory structure, and the version number in the path can change depending on which version of Visual Studio is performing your builds, so it's a good idea to make sure that your build server Visual Studio is the same version as your local Visual Studio.

The <Exec> node will remove the read-only attribute from the Web.config file if it exists, while the <CallTarget> node will initiate the ApplyTransform target defined in the lines before it.

This is just a down-and-dirty example, and one thing to note is that it will run every time, so you might be prompted by Visual Studio about the file being changed. To fix this, you could always add to the Condition attribute and check to make sure the configuration is not set to "Debug" before performing the transformation.