# Friday, August 31, 2007

For reference, here's how you update your app.config appSettings using MSBuild Community Tasks XmlUpdate.  This example actually batches several app.configs that need changing.

<XmlUpdate

  XmlFileName="%(ConfigFilesToCobrand.FullPath)"

  XPath="//configuration/appSettings/add[@key='MarketSegmentID']/@value"

  Value="$(MarketSegmentId)" />

Friday, August 31, 2007 8:12:56 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 

I think most people probably know how to version C# projects by now, but for my own future reference here is how I'm currently doing it. 

In my project's csproj file I append the following VersionProject target to the BuildDependsOn project.  You could override the BeforeBuild target, but appending a dependent target is safer according to Sayed Hashimi.  The following goes directly into each of my csproj files:

<PropertyGroup>

  <SourceDirectory>$(MSBuildProjectDirectory)\..\..\..</SourceDirectory>

  <ProjectDirectory>$(MSBuildProjectDirectory)</ProjectDirectory>

</PropertyGroup>

<Import Project="$(SourceDirectory)\utils\global.targets" />

<PropertyGroup>

  <BuildDependsOn>

    VersionProject;

    $(BuildDependsOn);

  </BuildDependsOn>

</PropertyGroup>

 

The VersionProject target is a custom target in my global.targets MSBuild project file which gets included into my csproj file.  This target is using the MSBuild community tasks' AssemblyInfo task.

<Target Name="VersionProject">

  <Message Text="Generating AssemblyInfo.cs Version: $(BuildVersion)"/>

  <MakeDir

    Directories="$(ProjectDirectory)\Properties"

    Condition="!Exists('$(ProjectDirectory)\Properties')" />

  <AssemblyInfo CodeLanguage="CS"

    OutputFile="$(ProjectDirectory)\Properties\AssemblyInfo.cs"

    AssemblyCompany="$(CompanyName)"

    AssemblyProduct="$(ProductName)"

    AssemblyCopyright="$(Copyright)"

    AssemblyVersion="$(BuildVersion)"

    AssemblyFileVersion="$(BuildVersion)"

    AssemblyTitle="$(ProductName)"

    AssemblyDescription="$(ProductName)"/>

</Target>

 

The great thing about this, I can reuse this target over and over again for each of my projects.  Some properties like the BuildVersion are set when running under CC.NET, others like CompanyName, Copyright are set once in the global.targets file as these don't change between projects.  By default my global.targets file will default the ProductName to the MSBuild project file name (minus the file extension), this of course can be overridden in any number of ways - including from CC.NET. 

The nice thing is, this will work when building from the command line with MSBuild.exe or from within Visual Studio.  This last point is important because we don't ever checkin the AssemblyInfo.cs source files to SVN (we set SVN ignore on these since they are autogenerated on every build) and most developers would like to checkout, open the solution, and finally CTRL SHIFT B.  Some OSS projects I've worked on require you to build from the command line first (to generate the AssemblyInfo.cs files) before you can build from Visual Studio; I believe this tends to confuse most .NET developers since most are so IDE dependant.

Another thing to note is that if a developer wanted to build a specifically versioned project from within Visual Studio all they would need to do would be to set an environment variable before starting Visual Studio, specifically: BUILDVERSION=1.0.0.1503.

Friday, August 31, 2007 7:07:50 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, August 30, 2007

Something I noticed today while cleaning up an MSBuild script is that there seems to be a direct correlation between the load time of bootstrapping MSBuild and your ItemGroups.  I noticed this because I have an ItemGroup that has an exclude to ignore any .svn folders on disk that I set at the parent folder level which contains a lot of files.

<ItemGroup>

  <System32Files

    Include="$(ProjectDirectory)\Libs\SdDriver\**\*.*"

    Exclude="$(ProjectDirectory)\**\$(SvnFolder)\**"/>

</ItemGroup

 

WHen I changed the above Exclude to $(ProjectDirectory)\Libs\**\$(SvnFolder)\** my build script load time was noticeably faster.  The interesting thing is, MSBuild doesn't count the loading of ItemGroups in its calculation of "Time Elapsed," running with either exclude setting produced identical Time Elapsed results - even though there was an obvious difference in run times.

Thursday, August 30, 2007 4:19:50 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Wednesday, August 22, 2007

I have multiple projects checked into my Subversion repository, but CC.NET out of the box will build all of them when any source file get checked into any part of the repository under trunk.  The desired behavior is to have only a build get kicked off when a source file gets checked in under its project folder.  I was able to do this when using Perforce with CC.NET by creating multiple views, but in SVN I haven't found a way to do this yet.

Unfortunately there doesn't seem to be a built in way to specify a URL filter in CruiseControl.NET for when a build gets kicked off in Subversion (I'm using CC.NET 1.3).  The current SVN source control provider in CC.NET only takes a trunk URL, and doesn't like it when you specify a subdirectory.    For now I've just hacked the Svn class in CC.NET to apply a filter using a new TrunkWatchUrl element.  In the following example, a build will only be kicked off when a change happens under http://svnserver/svn/gp/trunk/MyProject.

<sourcecontrol type='svn'>

  <trunkUrl>http://svnserver/svn/gp/trunk</trunkUrl>

  <trunkWatchUrl>http://svnserver/svn/gp/trunk/MyProject</trunkWatchUrl>

  <tagBaseUrl>http://svnserver/svn/gp/tags</tagBaseUrl>

  <workingDirectory>c:\autobuild\src\server</workingDirectory>

  <executable>C:\Program Files\Subversion\bin\svn.exe</executable>

  <username>domain\svcautobuild</username>

  <password>pass</password>

  <autoGetSource>true</autoGetSource>

  <tagOnSuccess>true</tagOnSuccess>

</sourcecontrol>

 

The associated code changes to Svn.cs:

[ReflectorProperty("trunkWatchUrl", Required = false)]

public string TrunkWatchUrl;

 

public override Modification[] GetModifications(IIntegrationResult from, IIntegrationResult to)

{

    ProcessResult result = Execute(NewHistoryProcessInfo(from, to));

    Modification[] modifications = ParseModifications(result, from.StartTime, to.StartTime);

 

    if (!string.IsNullOrEmpty(TrunkWatchUrl))

    {

        string trunkPath = TrunkWatchUrl.ToLower();

        modifications = Array.FindAll(modifications, delegate(Modification mod)

        {

            string[] folderParts = mod.FolderName.Split('/');

            if (folderParts.Length < 2)

                return false;

 

            string modFirstPathPart = folderParts[1];

            int trunkStartIdx = trunkPath.IndexOf(modFirstPathPart);

 

            if (trunkStartIdx < 0)

                return false;

 

            string localTrunkPathPart = trunkPath.Substring(trunkStartIdx).ToLower();

            return mod.FolderName.ToLower().Contains(localTrunkPathPart);

        });

    }

 

    if (UrlBuilder != null)

    {

        UrlBuilder.SetupModification(modifications);

    }

 

    return modifications;

}

 

Wednesday, August 22, 2007 10:59:51 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [4]  | 
# Sunday, August 19, 2007

Project structure is probably one of the most important things to get right.  If things are not setup in a consistent manner you will have a hard time managing your software builds.  I find convention over configuration to be of great help especially when creating new builds.  We want the project structure to be organized and easy to use, which means it should be easy for new developers to figure out where things are and where new files should go.  For this series I will be using the fictitious FoodBankTracker application.

I like to use multiple solution files at the root of my source control repository, this allows me to share projects between different solutions as project references are so much easier to deal with than file based assembly references.

The root of the source control repository

In the SharedLibs folder I have all my 3rd party file based assembly references which are used during the build process, things like: NUnit, Castle, NHibernate, RhinoMocks etc.  Other than these 3rd party assemblies, all of my references are project based.

In the Tools folder I keep build tools checked in such as NUnit, Doxygen, and MSBuildCommunityTasks.  The Tools folder differs from the SharedLibs folder in one key aspect, the SharedLibs folder contains assemblies that may be deployed with the built application, where the executables and assemblies in the Tools folder are only used during the build process.   Tools are checked into source control for several reasons:

  • New workstations don't require so many 3rd party tool installs.
  • Upgrades across the team are as easy as svn update.
  • Relative tool paths are fixed, which makes automating builds that much easier.

Tools directory

I also keep a generic global.targets MSBuild file in the Tools folder which gets included in all my MSBuild project files.  Global.targets keeps shared settings and targets that are common to all projects; this saves me time when configuring a new project, but it also allows me to change a tool path or target without touching every single build file in source control.  The global.targets also includes the MSBuildCommunityTasks which contains additional targets that will be used; by including it in global.targets its just one less thing I have to include in each build file.  Most importantly this target file keeps all the paths to the different tools used during the build process, remember we want to keep things consistent across projects.  This target build script also sets some build properties like version, company name, and sets some paths used during the build process.

Before global.targets gets included, the parent build script needs to set the $(SourceDirectory) property which should point to the root of the source control repository.  This path is used to find where things are in the source control repository relative to the trunk.  The parent build file may also want to set a friendly product name property, but this is purely optional.

 

<!-- The SourceDirectory property must be set prior to including this file -->

<!-- The ProductName property should be set prior to including this file -->

 

<PropertyGroup>

  <BuildVersion>$(CCNetLabel)</BuildVersion>

  <BuildVersion Condition="'$(BuildVersion)' == ''">0.0.0.0</BuildVersion>

  <CompanyName>Sneal</CompanyName>

  <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>

  <ProductName Condition="'$(ProductName)' == ''">$(MSBuildProjectName)</ProductName>

</PropertyGroup>

 

<PropertyGroup>

  <NUnitPath>$(SourceDirectory)\Tools\NUnit\bin\nunit-console.exe</NUnitPath>

  <DevEnvPath>&quot;$(VS80COMNTOOLS)..\ide\devenv.com&quot;</DevEnvPath>

  <DoxygenPath>$(SourceDirectory)\Tools\Doxygen\doxygen.exe</DoxygenPath>

  <MSBuildCommunityTasksPath>

    $(SourceDirectory)\Tools\MSBuildCommunityTasks

  </MSBuildCommunityTasksPath>

</PropertyGroup>

 

<Import Project=

"$(SourceDirectory)\Tools\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>

 

<PropertyGroup>

  <ArtifactDirectory>$(CCNetArtifactDirectory)</ArtifactDirectory>

  <ArtifactDirectory Condition="'$(ArtifactDirectory)' == ''">

    $(SourceDirectory)\artifacts\$(ProductName)

  </ArtifactDirectory>

  <PublishDirectory Condition="'$(PublishDirectory)' == ''">

    \\buildserver\builds\$(ProductName)\$(BuildVersion)

  </PublishDirectory>

  <TempZipDirectory Condition="'$(TempZipDirectory)' == ''">

    $(ArtifactDirectory)\ZipImage

  </TempZipDirectory>

</PropertyGroup>

 

Notice that this reusable targets file tends to be passive, i.e. it doesn't overwrite any properties if they've already been set.  One thing to note is that non-official CC.NET builds will be built using version 0.0.0.0, which would be used if a developer did a local build.  When CC.NET runs a build script it will set some additional properties, one of which would be build version.  Global.targets include some other paths and reusable targets that are not shown, but we won't talk about the reusable targets just yet.

Besides setting paths to build tools we also set properties for build directories, specifically: $(ArtifactDirectory), $(PublishDirectory), and $(TempZipDirectory).  The ArtifactDirectory property should point to where all build artifacts get spit out to.  Artifacts are things like XML result files from NUnit, MSBuild, and Doxyen output.  If running under CC.NET this will be the CC.NET artifact directory.  The TempZipDirectory property points to where all build output gets copied to, to build up a disk image in the proper layout for the zip file which will contain our ready for deployment build.  Everything in the temp zip directory gets zipped and then copied to the publish directory which is usually a network accessible location on the build server, here it would be \\buildserver\builds\FoodBankTracker\0.0.0.0

The commons folder contains a reusable library that I use between most, if not all, of my solutions.  This contains the actual library and another unit test project.  This project is built in the same fashion as the FoodBankTracker application, but will focus just on the one application.

FoodBankTracker application folder Now for the FoodBankTracker folder, without the FoodBankTracker application there would be no need for everything else we've talked about up to this point.  As you can see in the image, the application is broken out into four main logical areas: core, data, presentation, and web.  You will also notice that the main application and unit test projects are all at the same folder level and the database folder which contains the database scripts used to build the application's database.  There is generally a 1:1 ratio of unit test projects versus normal projects, but the important thing to note is that the test code is in a separate folder and project from the application code. 

All of the projects are class library projects except for the web project and the database project.  The web project is a normal web application project that only contains very thin display logic, while the database is a (.dbp) database project where we keep our SQL scripts. Solution explorer view of FoodBankTracker Inside the solution the different project types are logically organized between the core application and the Tests solution folder.  Separating out projects logically inside the solution instead of on disk allows me to easily move things around in the future without adversely effecting the build scripts.  Also since I've positioned my solution file at the root, I can easily add project references to my Commons library and unit test project.  This also allows me to add new shared "common" features that much quicker, because its just as easy to add it to the Commons project as it is to add it to one of the FoodBankTracker projects.

Sunday, August 19, 2007 4:34:01 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, August 09, 2007

I was browsing through our production web servers application event logs today and noticed a bunch of errors from .NET 2.0 Runtime Error Reporting about w3wp.exe crashing.  This naturally bothered me being a former systems administrator; it also bothered me because I was afraid that our asp.net application was the source.  After a couple hours of pair troubleshooting, we found that every time our app pool got recycled it was crashing, thus causing a nasty application event log message.  This was caused by the fact we were running our web server as Windows Server 2003 domain controllers.  The fix was to set the proper permissions (Query Value, Set Value, Create Subkey, Enumerate Subkeys, Notify and Read Control) on the following registry key for the Network Service account:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ASP.NET_2.0.50727\Names

For more info see this article: http://channel9.msdn.com/ShowPost.aspx?PostID=215428

Wednesday, August 08, 2007 11:19:12 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Tuesday, August 07, 2007
I got both my ReSharper and VisualSVN registration keys via email today!  It can't get much better than that.

Tuesday, August 07, 2007 10:01:46 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, August 06, 2007
This is almost blasphemous, but I'm disappointed with JetBrains right now.  If you don't know they make the excellent Re# plugin for Visual Studio.  I'm disappointed because my trial license of Re# 3.0 ran out and I had to go purchase the software.  I procrastinated a while but I wasn't worried about it because you can  buy it online and enter a license code to activate it.  That's what I thought until I didn't see my license email immediately show up.  After reading their website help I found this:

Delivery Information

JetBrains products are available via electronic delivery only. The product license details will be e-mailed to you within 2 business days of receipt of your order.

What?!  How can this happen in this day and age, especially from a company that makes kick-ass software? This means there must be a manual process in there somewhere, so now I'm stuck using regular Visual Studio all day.  I feel like I'm programming twice as slow today.

WTF
Monday, August 06, 2007 5:56:36 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, July 30, 2007

This post is using the CSAH Visual Studio plugin.  I like it for the following reasons:

  1. Includes an installer, so its brainless.
  2. It has options you can set to override your current Visual Studio settings like font.
  3. It appears to copy all my colors, including background colors.  WYSIWYG.
  4. Best of all it has a menu option in the context menu, Copy as HTML.

Lets try it, select some code in Visual Studio, right click Copy as HTML.  ALT-TAB to Live Writer, right click select Paste Special.  Select Keep Formatting, OK.  It keeps all the formatting info inline, so it should work in RSS aggregators.

/// <summary>

/// Gets or sets the data dictionary reference.

/// </summary>

/// <value>The data.</value>

protected IDictionary Data

{

    get

    {

        if (IsWeb)

        {

            return HttpContext.Current.Items;

        }

        else

        {

            LocalDataStoreSlot slot = Thread.GetNamedDataSlot(DataKey);

            return (IDictionary) Thread.GetData(slot);

        }

    }

}

Sunday, July 29, 2007 11:40:38 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 

This is something so simple, but so cool.  In FireFox I just noticed that I can select a piece of text on web page and right click select View Selection Source.  Only the HTML source for the selected area will be shown, all the other noise is gone.  I just double checked and IE doesn't have anything like this.

Sunday, July 29, 2007 11:22:04 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Sunday, July 29, 2007

For now obvious reasons, inserting code using an external style sheet DOES NOT work inside an RSS aggregator.  Hopefully I don't have to devolve into using Font tags as Jeff Atwood does.  Lets try the Live Writer plugin with the Embed StyleSheet option checked this time.

/// <summary>
/// Recursively gets the innermost exception, i.e. the first exception
/// that was thrown.
/// </summary>
/// <param name="ex">The exception to unwind.</param>
/// <returns>The inner most InnerException.</returns>
public static Exception GetInnerMostException(Exception ex)
{
    Exception inner = ex;
    while (inner.InnerException != null)
        inner = inner.InnerException;

    return inner;
}
Sunday, July 29, 2007 6:47:39 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 

By now most of us have figured out how to create sophisticated build scripts using NAnt, NAnt is the de-facto standard among .NET OSS projects and for good reason.  NAnt is the best build system on the .NET platform.  So with that out of the way, why would I be writing a series on MSBuild? 

Here's the biggest problem with NAnt as I see it, NAnt doesn't work in the de-facto .NET IDE, Visual Studio.  In Java, Ant build scripts work right there in your IDE.  As good as NAnt is, there's still the context switch cost of switching between Visual Studio and the command prompt.  Aw but I have hot keys you say.  OK, but some newbie dev on your team certainly doesn't, and in general most .NET developers are most comfortable within the realms of Visual Studio rather than the command line.  Like it or not, most .NET developers don't want to build through a command line before they can build through Visual Studio.

The other advantage MSBuild has is that csproj files work natively in the build engine.  If the C# project works in Visual Studio, then you know it will work on the command line with MSBuild.  There's no need to maintain two separate build scripts or any other silliness.  I have also found MSBuild to be really good at resolving dependant nested builds.  So without further ado, here's the rundown of posts I hope to hit in this series:

  1. The basics, project structure.
  2. Configuration file management.
  3. Building the database.
  4. Unit testing.
  5. Code documentation with Doxygen.
  6. Preparing your build artifacts for deployment.
  7. Integrating with CruiseControl.NET.
Sunday, July 29, 2007 5:56:21 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 

So I tried out the Windows Live Writer code formatting plugin available on CodePlex, and at first sight it seemed to work well, that was until I read my feed through Google Reader.  For some reason within Google Reader there was a bunch of extra white space between lines of code, which as a result made it completely unreadable.  So here I am again trying out yet another Live Writer plugin, Insert Code for Windows Live Writer.  Hopefully this one works.

/// <summary>
/// Recursively gets the innermost exception, i.e. the first exception
/// that was thrown.
/// </summary>
/// <param name="ex">The exception to unwind.</param>
/// <returns>The inner most InnerException.</returns>
public static Exception GetInnerMostException(Exception ex)
{
    Exception inner = ex;
    while (inner.InnerException != null)
        inner = inner.InnerException;

    return inner;
}

I'm using the instructions from Phil Haack.

Sunday, July 29, 2007 4:27:32 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  |