Passing Dynamic Parameters: Part 1, The Server
Dynamic What?
A common request on the mailing lists is whether parameters can be passed to the server when performing a force build. The rationale behind is this simple – rather than defining a large number of projects in the configuration they could just add one project with different options. Then at build time the user would decide which options to use.
Now, on the surface this appears to be a simple request – just add a simple dialog to the CCTray to allow the user to enter the parameters – and in CCTray it is simple. The complexity comes on the server-side: How are the parameter defined? How do the parameters modify the task? What values are allowed? Etc, etc, etc.
Additionally, there’s also the web dashboard to think about…
However, since this will definitely add value to CruiseControl.Net, I thought I would give it a try.
The Plan
Like most major changes this will require changes to three locations: the server, CCTray and the web dashboard. Since the server is the engine for the build process, I’ll start changes there and then move onto the client applications.
The configuration for the parameters will have two parts – but both will be within the project configuration. First there will be the list of parameters. This will include the name, data type and allowed values. This is the data that will be passed to the client applications so they know what to display. The second part of the configuration is the application of the parameters which will be set at the task level. This configuration will be used to match the received values from the client with the properties on the task.
The flow will be driven by the client. When a user clicks on the force build button (in either CCTray or the web dashboard), the client will send a request to the server for any parameters. If there are parameters, then these will be displayed to allow the user to change them. When the user has finished the parameters, or there are no parameters, then the request will be sent to the server.
Defining the Parameters
Since the parameters need to be passed to the clients I’ve added them to the remote project. As a base all parameters need to implement IParameter. This defines the base details required for each parameter. These are:
- Name: the name of the parameter. This is what will be passed between the client and the server. (required)
- DisplayName: an optional display name to be displayed to the user. This can be a more user-friendly name.
- Description: an optional description of the parameter to be displayed to the user.
- DefaultValue: the initial value of this parameter. This can be null - if the server receives null then it must know how to handle it.
- DataType: the actual data type of the parameter. (required)
- AllowedValues: for a parameter with a pre-defined set of results. If this is set then DataType must be string.
Additionally there is a method called Validate(). This allows for additional validation rules (e.g. min/max value, min/max length, regexs, etc.) This can be called on both the client-side and the server-side. However to be safe I’ll take the approach of never trusting the client input and always validating the input.
Since the parameters have many of the same properties, that won’t change with the various implementations, I’ve added a new base class called ParameterBase. This provides an implementation of Name, DisplayName, Description and DefaultValue. As this is an abstract class it can’t be used directly, but it does simplify the implementations of IParameter.
On top of this I’ve added three initial implementations: TextParameter, RangeParameter and NumericParameter. These simply provide parameter implementations that match their names (text, range and number).
These classes are as follows:

IParameter and classes
The following is an example of how these could be defined in the configuration:
<parameters> <textParameter> <name>Target</name> <description>The target to execute.</description> <default>Common.Build</default> </textParameter> <rangeParameter> <name>Condition</name> <allowedValues> <value>Dev</value> <value>Test</value> <value>Prod</value> </allowedValues> </rangeParameter> <numericParameter> <name>ErrorLevel</name> <display>Allowed Error Level</display> <minimum>0</minimum> <maximum>5</maximum> </numericParameter> </parameters>
This would be defined within the <project> element.
Using the Parameters
With the parameters now defined, the next step is to use them in a task. This involves another set of interfaces and classes. Since they are server specific, these interfaces and classes have been added to the core project.
Again, I have added a new interface to define a parameter usage scenario. This is called IDynamicValue. Unlike IParameter it has only one method: ApplyTo(). This method will literally apply one or more parameters to a property in the task.
I have provided two implementations of this interface: DirectDynamicValue and ReplacementDynamicValue. DirectDynamicValue takes a parameter and applies it directly to the property, while ReplacementDynamicValue uses a format string to transfer one or more parameters into a string to be set as the property (this just uses string.Format() internally). While this may seem like a small set to begin with, people can add their own implementations as desired – just need to implement IDynamicValue.
I also added a static class called DynamicValueUtility. This is a helper class for finding properties. As a developer I’m used to working with the property names on the different CC.Net tasks (or at least know where to find them). However the average user would just be used to the configuration file values – which are often different (even if just by casing). DynamicValueUtility uses reflection and the reflector attributes to find the properties based on the configuration names.
This class also uses dot notation for transversing down properties, plus it allows some ways of searching an enumeration. The user can do a keyed search, a positional search or a typed search.
A keyed search is when each item in the enumeration is searched to find a value that matches. This would take the expression “value[property=key]” and attempt to find an item in the “value” property that has a property called “property” with a value of “key”.
A positional search literally takes the item at the indexed position. This would take the expression “value[0]” and return the item at position 0 within the “value” property. If the index doesn’t exist then the item is ignored.
Finally the typed search uses the ReflectorType attributes to return the item. This would take the expression “value.type” and return the last instance of the type that implements “type” from the “value” property.
This helper class is a public static class, so it can be used anywhere, even in external libraries that provide new implementations of IDynamicValue.
The following diagram shows these classes:

IDynamicValue and classes
DynamicValueUtility has a couple of nested classes that are used internally. Additionally it exposes a number of methods that are mainly used internally, but can be used externally if required. The main method in this class is FindProperty().
Finally, the following is an example of how these classes would be defined in the configuration:
<dynamicValues> <directValue default="Common.Build" parameter="Target" property="projectFile"/> <replacementValue property="buildArgs" format="/p:BuildType={0} /noconlog /v:Normal"> <parameters> <parameter>Condition</parameter> </parameters> </replacementValue> </dynamicValues>
These are defined at the task level.
Applying the Parameters
So far I’ve covered how the dynamic parameters are defined, plus a little bit of how they are used. But I still haven’t modified the server to actually apply them.
There are two parts I need to add: the sending of the parameters to the client and the actual applying of the parameter values to the tasks.
The first part involves adding a new method to CruiseServer (and ICruiseServer, CruiseManager, etc.) called ListBuildParameters(). This will be called by the client before a force build is triggered. The client can still trigger a force build without calling this method (thus allowing the older clients to connect to a newer server).
Actually applying the parameters involved a lot more work. I modified ForceBuild() on CruiseServer to have a Dictionary<string, string> parameter, which will contain the actual parameters. I also added a new method to CruiseManager which contains this parameter – the older methods are still there but will pass in an empty dictionary.
Now CCTray uses Request() instead of ForceBuild(), so I also need to modify it. Request() takes in an IntegrationRequest instance, which is what gets generated by ForceBuild() (via a series of intermediary classes). So to simplify things I just added the dictionary directly to this class. The ForceBuild() method then passes the through the intermediary classes until the IntegrationRequest instance is generated.
Now after digging through the code I found the place that actually runs the project build is IntegrationRunner. This class has Build() and PostBuild() methods – which are the two methods that actually run tasks. Both of these methods call through to a number of methods on the individual project.
Which is where I ran into a problem – the individual projects implement IIntegrationRunnerTarget, which includes ITask. Therefore I can’t just expand the various methods to take in the dictionary as this would break every single task – including those in external libraries! And no way did I want to force this.
Dodging an Interface Problem
Since I can’t change the interfaces, I need some other way around being able to pass in parameters. To get around this I ended up adding two new interfaces:
- IParamatisedProject is a project that will take parameters. The Prebuild(), Run() and PublishResults() methods are all extended to include a parameters dictionary.
- IParamatisedTask is the task version. This has a new method called ApplyParameters(). Before the task is run this method will be called to change the required parameters.
When IntegrationRunner performs a build it checks to see if the project implements IParamatisedProject, if so it calls the extended methods, otherwise it calls the original methods.
I then modified Project so it implements IParamatisedProject. This involved adding the extended methods. These methods check each task before it runs to see if it implements IParamatisedTask. If so ApplyParameters() is called to set the parameters. After this check Run() is called normally.
Rather than modifying all the internal tasks to include the new interface I added a new abstract class called TaskBase. This class implements IParamatisedTask and provides a DynamicValues property (which is exposed to NetReflector).
The ApplyParameters() implementation merely loops through all the dynamic values (which must implement IDynamicValue) and applies them.
Coming Soon, the Clients
That’s it for this post. I’ve covered how I’ve modified the server to allow defining parameters, passing them to the client applications and then using them in a build. I’ve tried to do this in a way that won’t break existing configuration settings and it won’t break external tasks.
In my next post I’ll start modifying the clients. As always I’ll tackle CCTray first (since it’s normally a bit easier), and then I’ll handle the dashboard later.
Stay tuned…
Hi Craig,
I got a bunch of selectParameters working nicely with a sub-element of allowedValues. Works fine from CCTray and web interface.
However, when I create my own client, and call ListBuildParameters for the project, I get the full ParameterBase collection, but the allowedValues string array is always null.
Any ideas? It has to be something simple I’m missing, I reckon.
Thanks,
Wim
Hi,
This sounds good for us but i have a query-
1)Will this be applicable to cruisecontrol (not CC.Net)?
2) Instead of defining static values again under a project, can we have a interface which is used by client ,any dynamic value he enters would go inside that parameter value & force a build?
May be i am asking little silly question? I am a newbie to CC & want to use it for my release builds. If such feature exists then do let me know as it will be a great help.
regards
KK
Hi KK,
Sorry, this is only available for CC.NET – CC is a Java-based version, and my Java skills are not very good.
Craig