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
  {
    [Required]
    public ITaskItem Source { get; set; }

    [Required]
    public ITaskItem Transform { get; set; }

    [Required]
    public ITaskItem Destination { get; set; }

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

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

        // originalFileXml is now transformed
        originalFileXml.Save(Destination.ItemSpec);
      }

      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="http://schemas.microsoft.com/developer/msbuild/2003">
  <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')"/>
  </Target>
</Project>

* 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!

Advertisements

4 thoughts on “Mono ASP.NET project deployment with Web.config XDT transformations

  1. Hi Marcelo…

    Quick note on using this: I didn’t get very far before I ran into an issue. The trivial example I had working only had one xdt:Transform tag in it…

    Once I started adding other transforms, (xdt:Transform=”SetAttributes(Username,Password)” for example), I started getting an error:

    An error occurred while applying transformation to ‘web.config’ in project ‘MyTestProj’: Argument cannot be null.
    Parameter name: key

    A Google search led me to https://github.com/mrward/monodevelop-nuget-addin/issues/16 … I grabbed a copy of mrward’s custom Microsoft.Web.Xdt.dll and referenced that one instead. Problem solved. Just thought I’d document that here in case someone else runs into it.

    1. My single transform example that was supposed to appear after paragraph one appears to have been stripped out by WordPress:

      system.web
      compilation xdt:Transform=”RemoveAttributes(debug)” /
      /system.web

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s