Pages

Sunday, April 8, 2012

ASP.NET Web Application's Build Using NAnt

Introduction

Nowadays nightly builds are the most important entity for any project development lifecycle. NAnt is one of the most popular tools for .NET automated builds. NAnt is the same as Ant but it is specially designed for .NET.
This article will explain the procedure for writing the build file for an ASP.NET application in brief. I am expecting that the reader knows the basics of XML technology and deployment of an ASP.NET application.

Background

The deployment of an ASP.NET application is little bit tricky. When you build the ASP.NET application it generates a separate DLL file for each class library included in the project. However, ASP.NET 2.0 does not generate a DLL file for your code behind files, i.e. ".cs" in case of C#. We have to use aspnet_compiler.exe, which comes along with the .NET 2.0 Framework for generating DLL files for CS files. Again it generates a separate DLL for each CS. All this makes build and deployment processes very complicated.
Here I am trying to guide you on how to write a build file for ASP.NET's application automated build using NAnt. NAnt is a very popular build tool. It's open source and you can download it freely from SourceForge. To use this code, you have to download some external tools to setup your dev environment.
  1. NAnt
  2. Visual Studio 2005 Web Deployment Projects

Using the Code

As mentioned earlier, you have to first install "Visual Studio 2005 Web Deployment Projects." Just download it from the MSDN site and install it on a default location. This installation is required to merge multiple DLL files generated by MSBuild.exe.
Download NAnt and copy the folder in to C: (you can copy it into any directory; I am giving C: just as an example and I have used the same path in the build file given below). Now go to the C:\nant\nant-0.85\bin folder. Here you can see a set of some XML/XSL files and one "lib" folder.
Create one new file here and name it as build.number.xml. This file will specify the current version of the project in the source control. Copy the following contents and save the file.
xml version="1.0" encoding="utf-8"?>
<build>
    <major>1</major>
    <minor>0</minor>
    <build>0</build>
    <release>0</release>
    <companyname>CompanyName</companyname>
    <copyright>Copyright 2007</copyright>
</build>
The schema of the above file should be in the same folder. Create one file and name it as genVer.xsl. Copy the following code and save the file.
xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:template match="/">

#define VER    <xsl:value-of select="build/major"/>,
               <xsl:value-of select="build/minor"/>,
               <xsl:value-of select="build/build"/>,
               <xsl:value-of select="build/release"/>

#define STRVER     "<xsl:value-of select="build/major"/>,
                    <xsl:value-of select="build/minor"/>,
                    <xsl:value-of select="build/build"/>,
                    <xsl:value-of select="build/release"/>"

#define COMPANYNAME "<xsl:value-of select="build/companyname"/>"

#define COPYRIGHT "<xsl:value-of select="build/copyright"/>"
        
    </xsl:template>
</xsl:stylesheet>
Now we have to do some XSL coding which will help us to increment the version number. So create one new file in the same folder and name it as IncrBuild.xsl. Copy the following code and save the file.
xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="/build/build">
        <xsl:variable name="buildNumber" select="."/>
        <xsl:element name="build">
            <xsl:value-of select="$buildNumber + 1"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="/ | @* | node()">
        <xsl:copy>
          <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>
Now you are ready to write an actual build file. By default, the nant folder contains one file: Nant.build. This is a simple XML file where you can write your build code. You can use a separate file for your build code with the extension ".build." You can run this build code using nant.exe. There are several options available with this EXE file. You can read it from NAnt documentation available here.
I am not giving NAnt coding basics here. You can read it from NAnt's help. So let's start writing the heartbeats for your automated build file. Open the NAnt.build file in any text or XML editor. First, create the rootnode for which you will specify the name of the project and default target to which NAnt.exe will execute to start the build process.
<project name="ProjectName" default="buildProject" 
    xmlns="http://nant.sf.net/schemas/nant-0.85.win32.net-1.0.xsd">
</project>
Between the project tag, we have to write some targets and properties. To make our code more generic and configurable, we have to declare some properties first. Please see code comments for description.

    
    <property name="BuildDir" value="D:\Work"/> 
    
    <property name="target" value="D:\Work\deploy" overwrite="false" />
    
    <property name="SolutionFileName" value="NantTrial.sln"/>

    
    <property name="BuildNumberPath" 
        value="C:\nant\nant-0.85\bin\build.number.xml"/>
    
    <property name="dotnet" 
        value="c:/windows/Microsoft.NET/Framework/v2.0.50727" 
        overwrite="false"/>
    
    <property name="MSBuildPath" 
        value="C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe" />
    
    <property name="WebDeploymentExePath" 
        value="C:\Program Files\MSBuild\Microsoft\WebDeployment\v8.0" />
    

    <property name="WebProjectFolderName" value="NantTrial" />
    
    <property name="FinalDeployDllName" value="ProjectName" />
    
    <property name="newReleaseLabel" value="ProjectName-0-0-0-0"/>
    
    <property name="build.version.major" value="0"/>    
    
    <property name="build.version.minor" value="0"/>    
    

    <property name="build.version.build" value="0"/>    
    
    <property name="build.version.revision" value="0"/>    
    
You can configure the above properties as per your requirements. There are a total of four targets in the build file.
  1. buildProject [Default target responsible for building the .NET application and creating DLL files in the deploy folder]
  2. clean [Remove the previous deploy folder]
  3. setversion [Set version string in application's AssemblyInfo.cs file]
  4. setVersionString [Set version string property, which can be used for labelling the source or naming the deply folder]
  5. incrementBuildNumber [This will execute XSL file to increment current version by 1]
All these properties depend on each other and will be executed in the bottom-up manner (5 to 1). You can configure this as per your requirements using the target's "depends" property. You can download the attached sample NAnt.build file for the above targets. The key part of the article lies in the buildProject target. Here we are going to build the web application.
To build the application, we have to use .NET's command line utility MSBuild.exe. This file lies in the .NET Framework's folder by default. This file takes the solution file name as an argument. Execute it using following code.
<exec program="${MSBuildPath}">
    <arg value="${BuildDir}\${SolutionFileName}" />
</exec>
Now the code can be compiled using aspnet_compiler.exe. This file also comes along with the .NET Framework installation and resides in the same folder. aspnet_compiler.exe has multiple command line options. You can use it based on your needs. The complete set of options are described here. Execute aspnet_compiler.exe using following code.
<exec program="${dotnet}/aspnet_compiler.exe" 
    commandline=
    "-f -u -p ${BuildDir}\${WebProjectFolderName} -v / ${target}"/>
This tool will generate a separate DLL for your class libraries, but the problem is that it also generates separate DLLs for every CS file. It's not a good idea to ship your build with these multiple DLL files and have one for each web page. So, there is need to merge these multiple DLL files into one final deployable DLL.
You can do this using Visual Studio 2005 Web Deployment Projects. Installation of Web Deployment Projects copies aspnet_merge.exe to your build machine. It takes the target folder name and name for the final DLL as an arguments. Execute aspnet_merge.exe using following code.
<exec program="${WebDeploymentExePath}\aspnet_merge.exe" 
    commandline="${target} -o ${FinalDeployDllName} -xmldocs"/>
This whole process will copy some unnecessary files to target the deployment folder, which we need to remove. Remove these files using:
<delete>
    
        <fileset>
            <include name="${target}/*.build" />
            <include name="${target}/*.scc" />
            <include name="${target}/*.sln" />
            <include name="${target}/build.*" />
        </fileset>
</delete>
Now open the command prompt. Change the directory to your NAnt folder (i.e. C:\nant\nant-0.85\bin\ in my case). Type Nant.exe and press "Enter." The build process will start. You can see the verbose on the command line. If the build succeds, it will give the following message:
BUILD SUCCEEDED

Total time: 33.3 seconds.
Now check your "target" folder, i.e. the value of the property "target" in the build file. In my case it is D:\Work\deploy. All web pages (ASPX) should be there with one bin folder and the precompiledApp.config file. The bin folder should contain all DLLs for class libraries and one DLL for your CS file instead of seperate DLLs for each. The name of the DLL should be the value of property FinalDeployDllName. In my case it is ProjectName.
Just rock!! You are now ready for deployment. The main advantage of NAnt automated builds is that you can schdule your nightly builds. Just create one BAT file with the command NAnt.exe and schedule its running time using Windows scheduler.

Points of Interest

While designing an ASP.NET application one should keep in mind not to keep your solution file in the same folder where your web pages are being stored. MSBuild.exe will fail to perform build in this case.
I am planning to write a separate article for NAnt's common targets like source control tasks, send mail, copy files to folders on server, etc. I will start them shortly.
The versioning targets are being referred from here. Go through this nice blog on versioning using NAnt.

No comments: