My Blahg

August 5, 2008

CruiseControl.NET and multiple build configurations: Part 5 – My custom template

Filed under: Continuous Integration/CruiseControl.NET, NAnt, c#, dotnet — treyhutcheson @ 3:38 pm

[More Posts On This Topic]

It’s been a long few months since my last post in this series. For that I apologize. Today I resume the series with the discussion of the custom template I’ve used for my properties plugin.

Let’s not waste time and jump right into the template. It’s not very large:

<h2>NAnt properties for project $projectName</h2>

<table>
	<tr>
		<td width="20" />
		<td width="350" >The properties defined on this page are passed to the NAnt executable via the command line as
		NAnt properties.</td>
	</tr>
</table>
<br />
#if ($props.Count == 0)
	No properties
#else
	<form method="post">
	<table class="section-table" cellspacing="0" border="0">
		<tr class="sectionheader">
			<td width="150">Property Name</td>
			<td width="200">Value</td>
		</tr>
		#set( $rowCounter = 0 )
		#foreach( $prop in $props )

			#if ( $rowCounter % 2 == 0 )
				#set( $rowClass = 'section-oddrow' )
			#else
				#set( $rowClass = 'section-evenrow' )
			#end

			<tr class="$rowClass">
				<td>$prop.name</td>
				<td><input type="text" name="row$rowCounter" value="$prop.value"></td>
			</tr>

			#set( $rowCounter = $rowCounter + 1 )
		#end
		<tr>
			<td colspan="2" align="right"><br /><input type="submit" name="Refresh" value="Commit"/></td>
		</tr>

	</table>

#end

Before we examine the template, here is the output of the template:

Output of the template

Output of the template

As you can see, it’s a simple and dirty page that’s not very pretty. However, it is functional. Now that you’ve seen the complete template and it’s output, lets examine it piece-by-piece.

Header
The header section of the template looks like this:

<h2>NAnt properties for project $projectName</h2>

<table>
	<tr>
		<td width="20" />
		<td width="350" >The properties defined on this page are passed to the NAnt executable via the command line as
		NAnt properties.</td>
	</tr>
</table>
<br />

The template starts out with a simple H2 header element, and displays the name of the project via a reference to the $projectName variable. After that, it outputs a short description of what this page does. Pretty simple html so far.

No properties defined?
The next section of the template looks like this:

#if ($props.Count == 0)
	No properties
#else

This section determines if there are any property objects available for the template. The $props variable will be published to the template via the plugin, so we can refer to it here. If there are no properties, then we just output the text “No properties”. Short and sweet.

What’s interesting to note here though is Velocity’s support of dot-notation evaluation. Notice the text “$props.Count”? This text causes Velocity to evaluate the $props object for a property called Count. So as long as the $props object has a public property named Count and can be compared to the literal ‘0′, we’re in good shape. We need to remember that when we advertise the object to the template when the template is generated.

Forms and post-back
The next bit of code declares a form, containing a table and a couple of table columns. Notice the form declaration has the method defined, but is not named nor does it define an action. I found that this declaration allows the form to post back to itself; actually, I borrowed this behavior from other templates distributed with the ccnet dashboard.

Displaying the defined properties
The rest of the template simply loops through all of the properties defined, and outputs the name and value of each.

Notice the use of the $rowCounter variable? It’s defined before the looping begins. For each iteration of the loop, $rowCounter is evaluated by modulus 2 to determine if we are on an odd or even row, and sets the style accordingly. This lets each row be shaded on or off.

Additionally, the $rowCounter variable serves another purpose: it allows us to refer to each input box for each property’s value. When the property name and value are emitted, the value is placed within an input text element. The name of the text element takes the form of “row$rowCounter”, which will expand at runtime to be “row0″, “row1″, etc. We’ll use that later in the plugin when processing the postback.

Here’s the the code listing for the rest of the template:

<form method="post">
	<table class="section-table" cellspacing="0" border="0">
		<tr class="sectionheader">
			<td width="150">Property Name</td>
			<td width="200">Value</td>
		</tr>
		#set( $rowCounter = 0 )
		#foreach( $prop in $props )

			#if ( $rowCounter % 2 == 0 )
				#set( $rowClass = 'section-oddrow' )
			#else
				#set( $rowClass = 'section-evenrow' )
			#end

			<tr class="$rowClass">
				<td>$prop.name</td>
				<td><input type="text" name="row$rowCounter" value="$prop.value"></td>
			</tr>

			#set( $rowCounter = $rowCounter + 1 )
		#end
		<tr>
			<td colspan="2" align="right"><br /><input type="submit" name="Refresh" value="Commit"/></td>
		</tr>

	</table>

#end

Great – we have a template. Now what?

Generating the template
We’ve already covered how templates are generated at runtime by the dashboard. Now we can discuss how to generate this specific template from the properties plugin.

The first thing we need to do is to determine if the template is posting-back, or if the template is being generated from scratch. Why? Because if it’s a post back, we need to commit/save the modified properties, otherwise we need to publish the properties to the template afresh. So how do we do that? More specifically, how does the dashboard tell us that the request is postback?

Fortunately for us, the dashboard stuffs a parameter into the request called “Refresh”. So all we have to do is check the request for this parameter. It doesn’t matter matter what the parameter’s value is. If the parameter exists, it’s a postback. Now we need to know how to interrogate the request for parameters. Fortunately the IRequest interface is not that complicated, and it exposes a method named FindParameterStartingWith. From what I can determine, this method does not actually return the parameter value, merely the parameter key. So, using this behavior, we can define a new method for our plugin:

/// <summary>
/// Determines if the current page view is a normal view, or if the form has
/// been submitted as a postback(and subsequently the modified property values will
/// need to be committed back to the ccnet server).
/// </summary>
private bool IsRefresh( IRequest request )
{
  string key = request.FindParameterStartingWith( "Refresh" );
  return key != string.Empty;
}	//END IsRefresh method

Now that we know if the page is a postback or not, what do we do next? Since we’re publishing properties, we need to know what properties to publish to the template. Additionally, if the request is indeed a postback, we need to commit the saved properties.

Here’s my full implementation of the ICruiseAction.Execute method:

/// <summary>
/// Executes the request
/// </summary>
public IResponse Execute( ICruiseRequest cruiseRequest )
{
	// get the request and the current project
	IRequest req = cruiseRequest.Request;

	// read all of the properties as they are currently defined in the project's config
	List<PropertyItem> existingProperties = ReadItems( cruiseRequest.ProjectSpecifier );

	// determine if we are displaying the properties, or committing the properties
	if( IsRefresh( req ) )
		return Commit( existingProperties , cruiseRequest );
	else
		return ExecuteNew( existingProperties , cruiseRequest );
}	//END Execute method

This is a pretty short method implementation. The first thing it does it acquire the actual IRequest reference. Next it reads the properties that are defined for the current project(notice the reference to the cruiseRequest.ProjectSpecifier property?). Afterwards, it calls the IsRefresh method(which we already detailed) and either calls Commit or ExecuteNew. Let’s not worry about the Commit method just yet – it will demand another post entirely. For now let’s concentrate on the calls to ReadItems and ExecuteNew.

ReadItems method
From the Execute implementation, you can infer that the ReadItems method returns a List of type PropertyItem. The PropertyItem class is very simple, and I won’t waste time discussing it in detail. The full listing follows:

/// <summary>
/// This type reflects custom properties defined in the config file, as child elements to the
/// parameterizedNant task. PropertyItem objects are simple name/value pairs.
/// </summary>
[ReflectorType( "property" )]
public class PropertyItem
{
	/// <summary>
	/// The name of the property
	/// </summary>
	private string _name;

	/// <summary>
	/// The value of the property
	/// </summary>
	private string _value;

	/// <summary>
	/// Default constructor
	/// </summary>
	public PropertyItem()
		: this( string.Empty , string.Empty )
	{
	}	//END constructor 

	/// <summary>
	/// Parameterized constructor to initialize the object with its name/value pair
	/// </summary>
	/// <param name="name">the name of the property</param>
	/// <param name="value">the value of the property</param>
	public PropertyItem( string name , string value )
	{
		_name = name;
		_value = value;
	}	//END constructor 

	/// <summary>
	/// Gets/Sets the name of the property
	/// </summary>
	[ReflectorProperty( "name" )]
	public string Name
	{
		get
		{
			return _name;
		}
		set
		{
			_name = value;
		}
	}	//END Name property

	/// <summary>
	/// Gets/Sets the value of the property
	/// </summary>
	[ReflectorProperty( "value" )]
	public string Value
	{
		get
		{
			return _value;
		}
		set
		{
			_value = value;
		}
	}	//END Value property

	public override string ToString()
	{
		return string.Format( "{0}={1}" , _name , _value );
	}	//END ToString method 

}	//END PropertyItem class

The ReadItems needs to read the nant properties defined for a given project configuration. Out of the box, ccnet does not give us this behavior. That means we will be developing a server-side plugin that is a sister to the dashboard plugin. Like the implementation of the Commit method, that topic is *way* too deep for this post. So for right now, just implement the ReadItems method so it returns a mock list of PropertyItem objects.

ExecuteNew method
This post is getting pretty long, so fortunately the ExecuteNew method is pretty simple. The purpose of the ExecuteNew method is to take a list of PropertyItem objects, and publish them to the template so they can be available at evaluation time. This method is only executed for new page requests, and not for postbacks. Here’s the full implementation of the ExecuteNew method:

/// <summary>
/// Generates a new view from scratch with the property items that are presently defined.
/// </summary>
private IResponse ExecuteNew( List<PropertyItem> properties , ICruiseRequest cruiseRequest )
{
	Hashtable map = new Hashtable();
	map.Add( "props" , properties );
	map.Add( "projectName" , cruiseRequest.ProjectName );

	return _viewGenerator.GenerateView( "ViewProjectProperties.vm" , map );
}	//END ExecuteNew method

When you looked at the template, did you think it would be difficult to add a list of objects? Fortunately for us, it’s exceedingly simple. Remember that the hashmap published to the template is a simple string/object affair. So to publish the properties, we simply add them to the hashtable with the key of “props”. After that, it’s nothing that hasn’t already been covered.

Here would be a good time to point out how stupid it is to use string literals. Just remember, this is nothing more than a blog post :) .

** Styles
The template I list earlier referenced a few different styles. These styles are defined within the file “cruisecontrol.css” and is in the root of the dashboard’s folder. I’m sure you are free to define your own styles in your template or even in an external css file, but I just played around with the template until I found something that wasn’t too obnoxious.

The next post will cover the topic of the server-side plugin. At this point you’re probably wondering why we even need one?

No Comments Yet »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.