Deploying ASP.NET Web Applications using NAnt

We have started implementing Contiuous Integration at our company. We began with Cruise Control.NET but have decided to try with TeamCity instead. One thing that we would like to do is to deploy ASP.NET web applications as part of our Continuous Integration process. We use the Web Application Project template in Visual Studio. One problem that we faced was: How can we copy all the neccesary files to the web server? We didn’t want to specify the files explicitly nor did we want to copy all files with certain file name extensions. Those approaches seemed very error-prone.

The first approach we used with Cruise Control.NET was to use the MSBuild target _CopyWebApplication which is defined in Microsoft.WebApplication.targets. I was not very pleased with this solution, although it worked. My main concern that we didn’t have any control over the process. When we started with TeamCity it turned out that this approach was not working anymore. The _CopyWebApplication target copies all files needed for the web application to a directory called _PublishedWebSites\<web application project name>. For some reason (probably a good one), TeamCity mangles the name of the project file, with the effect that the name of the directory where the web application files were copied to had a different name than expected.

So I decided to try another approach. Since I like NAnt much more than MSBuild, I started to explore if it was possible to use NAnt for this task. The main problem was that the valuable information on which content files were part of the application was buried in the project file. To utilize this information I used an XSL transformation to transform parts of the project file (which is an MSBuild script) into a NAnt script.

We would like to select all the files where the Build Action property is set to Content. Those files are listed in the project file as a Content element, like in this example:

  <ItemGroup>
    <Content Include="Default.aspx" />

Hence they can be selected with the XPath expression: /Project/ItemGroup/Content.

What I wanted my XSLT script to do was to create a NAnt script with the following content:

  1. A fileset with all the content files included
  2. A target which copies the files in the fileset to some directory defined by a property.

This is what I came up with:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt"
    exclude-result-prefixes="msxsl"
    xmlns:s="http://schemas.microsoft.com/developer/
        msbuild/2003">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="/">
        <project
            xmlns="http://nant.sf.net/release/0.85/nant.xsd"
            name="GeneratedFromMsbuild">
            <fileset id="content.files"
                basedir="${{project.root}}">
                <xsl:for-each select=
                    "/s:Project/s:ItemGroup/s:Content">
                    <include name="{@Include}"/>
                </xsl:for-each>
            </fileset>
            <target name="copy.content">
                <copy todir="${{destination.dir}}">
                    <fileset refid="content.files"/>
                </copy>
            </target>
        </project>
    </xsl:template>
</xsl:stylesheet>

The following excerpt from our main NAnt script shows how the XLS transformation is invoked and how the resulting script is used to copy the content files:

<target name="deploy.web" depends="generatenant">
    <nant buildfile="${generated.file}" target="copy.content">
        <properties>
            <property name="project.root"
                value="${web.projectrootdir}"/>
            <property name="destination.dir"
                value="${web.rootdir}"/>
        </properties>
    </nant>
   <copy
       todir="${web.rootdir}\bin">
       <fileset basedir="${web.projectrootdir}\bin">
            <include name ="*.*"/>
       </fileset>
   </copy>
</target>

<target name="generatenant">
    <delete file="${generated.file}" failonerror="false"/>
    <style style="GenerateNantFromMSBuild.xslt"
        in="${web.projectrootdir}/${web.projectfilename}"
        out="${generated.file}"/>
</target>

Closing remarks

Of course there are several other ways of accomplishing this, like using a Web Deployment Project, but I personally like having full control of what is going on and that can be achieved with NAnt.

Advertisements
Previous Post
Leave a comment

6 Comments

  1. Luke

     /  April 22, 2009

    We use nant and wix to create an msi. The msi contains a dropdownlist specifying target env configurations, that copy in the appropriate local.web.config. We then have a bunch of deploy projects that use psexec to copy over the msi, uninstall the old version and install the new version. Works a treat in teamcity.

    Reply
  2. Excellent site, keep up the good work

    Reply
  3. Thanks! I’ve started to use this same method in my production and test deployment projects. I was using the VS Web Deployment projects before, but they always caused me problems. This is much simpler!

    Reply

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

%d bloggers like this: