Marcelo Zabani's coding blog

Mono ASP.NET project deployment with Web.config XDT transformations


One thing that I’ve always wanted in the Mono stack was the ability to deploy ASP.NET MVC applications from a terminal, and to get Web.config xdt transformations along the way (how great would that be!?). Today I managed to cook something! It is not perfect, but it works pretty well for my needs and will probably be fine by most people’s standards. Let’s get to it already!

You will need a target for xbuild (Mono’s MSBuild replacement) and a MSBuild Task called TransformXml, responsible for XDT transformations. To build this Task you will also need a library called Microsoft.Web.Xdt. Let’s do it step by step.

First, create a library project called WebTasks (you can actually choose whichever name you prefer, just remember to change it in the target file later) in Monodevelop and add the following class to it:

using System;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
using Microsoft.Web.XmlTransform;

namespace MSBuildTasks
  public class TransformXml : Task
    public ITaskItem Source { get; set; }

    public ITaskItem Transform { get; set; }

    public ITaskItem Destination { get; set; }

    public override bool Execute () {
      var originalFileXml = new System.Xml.XmlDocument();

      using (var xmlTransform = new XmlTransformation(Transform.ItemSpec)) {
        if (xmlTransform.Apply(originalFileXml) == false)
        return false;

        // originalFileXml is now transformed

      return true;

Grab a copy of Microsoft.Web.Xdt, add a reference to it, add references to Microsoft.Build.Utilities and Microsoft.Build.Infrastructure too (these should be in the GAC) and build this library, holding on to the generated assembly file.
* Warning: This is most likely not a complete implementation of the TransformXml Task. Since I couldn’t find any decents docs on it and it serves its purpose, I’ll leave it at that.

Now, this is the target I created to actually deploy my files to a specified destination (copy and paste it to a file called WebDeploy.targets or whatever name you want!):

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="">
  <UsingTask TaskName="TransformXml" AssemblyFile="WebTasks.dll" />
  <Target Name="Deploy" Condition="'$(DeployDir)' != '' And !$(SkipCopyUnchangedFiles)" DependsOnTargets="Build">
    <Copy SourceFiles="@(FileWrites)" DestinationFolder="$(DeployDir)\%(RelativeDir)" />
    <Copy SourceFiles="@(Content)" DestinationFolder="$(DeployDir)\%(RelativeDir)" />
    <TransformXml Source="Web.config" Transform="Web.$(Configuration).config" Destination="$(DeployDir)\Web.config" Condition="Exists('Web.$(Configuration).config')"/>

* Important: Microsoft.Web.Xdt.dll, WebTasks.dll and WebDeploy.targets must all be in the same directory!
* Monodevelop insisted in trying to import a targets file from $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets, resulting in an error everytime I tried to build my project, since the v10.0 folder didn’t exist in my Mono installation. To solve it, I just symlinked it to v9.0.

We are almost there! This is our last step. Open your ASP.NET project’s file and add the following line to it (you can add it near the other imports for elegance’s sake):

<Import Project="RelativePathTo\Deploy.targets" />

Now that everything is set up, just run:

$ xbuild ASPNETProject.csproj /t:Deploy /p:Configuration=Release /p:DeployDir=deployDirRelativeToProjectFile /p:SkipCopyUnchangedFiles=false

And the files are now correctly deployed!
* Caveats:
– I have only tested this with MY ASP.NET MVC project. Although the only bad thing I’ve noticed so far is that the “obj” folder and mdb files inside the bin directory get copied too, it certainly needs more testing.
– You have to set SkipCopyUnchangedFiles to false or you will run into trouble in the future. We rely on a property called FileWrites set by one of the targets used in the build process. If you set SkipCopyUnchangedFiles to true, FileWrites will not include files from your project’s dir if they haven’t been modified, which could lead to some files not being deployed if you, for instance, keep building with the IDE and after many consecutive builds decide to deploy with this script. In fact, the target I wrote will not run if you don’t set it to false.

If you use it and have any problems, I’d really like to know!