Automated Coder

Exploring the Code of CruiseControl.Net

Archive for the ‘NetReflector’ Category

Extended Configuration – Beyond NetReflector

Posted by Craig Sutherland on 11 May, 2009

Some Background

In CruiseControl.NET we have a file merge task. This task will load one or more files and merge them into a single XML document. From this document the reports get generated.

Recently we made some changes to CruiseControl.NET to allow reports and report artefacts to come from a different location, which now allows images and HTML reports to be included. This was actually a flow-on effect from the new NDepend and NCover tasks, as these tasks needed to display non-XML data.

These worked so well, one of the other developers asked how to include items from other tasks in these reports, and even potentially user-specified files. The bad news at the time is this was not possible. The only merging task we had was the merge files task (<merge> element) and this task merges into the XML document.

This left us with two options:

  1. Add a new task to merge non-XML files
  2. Modify the merge task to do non-merge copying

Since I didn’t particularly like the idea of a new task just for this specialised functionality, I decided to look at extending the current merge task.

The Problem

An example configuration for the current merge task would look something like this:

<merge>
    <files>
        <file>File 1</file>
        <file>File 2</file>
        <file>File 3</file>
    </files>
</merge>

In contrast, this is what I wanted to achieve:

<merge target="somewhere">
    <files>
        <file>File 1</file>
        <file action="Copy">File 2</file>
        <file action="Merge">File 3</file>
    </files>
</merge>

The problem with this lies in the action attributes – NetReflector does not handle them. The closest we can get is something like:

<merge target="somewhere">
    <files>
        <file>File 1</file>
        <file><action>Copy</action><name>File 2</name></file>
        <file><action>Merge</action><name>File 3</name></file>
    </files>
</merge>

Which would break existing config files! So, based on plain-vanilla NetReflector we would be stuck.

But, there is a way around this, and one that doesn’t involve modifying NetReflector at all.

Expanding the Attribute

All of the configuration is based on NetReflector attributes and most of it uses ReflectorPropertyAttribute. Like all the NetReflector attributes it has a constructor that requires a name, which is the name of the element. However, this attribute is unique – it also has a second constructor allows the developer to pass in a factory Type.

The factory argument allows the developer to override the default serialisation/de-serialisation behaviour with their own custom behaviour.

The factory type must implement ISerialiserFactory. This is a simple interface with one method that creates a new serialiser. This serialiser can then serialise/de-serialise in any way it wants!

The serialiser must implement IXmlMemberSerialiser, which also includes IXmlSerialiser. IXmlSerialiser has a Read() and a Write() method, while IXmlMemberSerialiser provides two properties and an extra method (I haven’t figured out what they are needed for yet!)

While it is possible to directly implement these interfaces, a simpler way is to inherit from XmlMemberSerialiser. This class implements all of the methods and properties – we just choose which ones we want to override (normally Read() and Write()).

Both the Read() and the Write() methods work directly with XML. The Read() method receives in the node to be de-serialised, while the Write() method receives an XmlWriter to work with. Generally, if you going to override one, you should override both – especially as both are used in CruiseControl.NET.

The only down-side to this approach is you have to completely implement the serialisation/de-serialisation behaviour. The default serialiser automatically handles whether it is an array, a list or a single value, our custom serialiser needs to do this for itself.

Reading a Node

As an example of how to implement a custom serialiser, I’ll go over what I have done for the new serialiser for the merge file task. This serialiser handles the desired XML I showed above.

The code for the Read() method is:

public override object Read(XmlNode node, NetReflectorTypeTable table)
{
    var fileList = new List<MergeFileInfo>();

    if (node != null)
    {
        // Validate the attributes
        if (node.Attributes.Count > 0)
        {
            throw new NetReflectorException("A file list cannot directly contain attributes.\r\nXML: " + node.OuterXml);
        }

        // Check each element
        foreach (XmlElement fileElement in node.SelectNodes("*"))
        {
            if (fileElement.Name == "file")
            {
                // Make sure there are no child elements
                if (fileElement.SelectNodes("*").Count > 0)
                {
                    throw new NetReflectorException("file cannot contain any sub-items.\r\nXML: " + node.OuterXml);
                }

                // Load the filename
                var newFile = new MergeFileInfo();
                newFile.FileName = fileElement.InnerText;

                // Load the merge action
                var typeAttribute = fileElement.GetAttribute("action");
                if (string.IsNullOrEmpty(typeAttribute))
                {
                    newFile.MergeAction = MergeFileInfo.MergeActionType.Merge;
                }
                else
                {
                    try
                    {
                        newFile.MergeAction = (MergeFileInfo.MergeActionType)Enum.Parse(
                            typeof(MergeFileInfo.MergeActionType),
                            typeAttribute);
                    }
                    catch (Exception error)
                    {
                        throw new NetReflectorConverterException("Unknown action :'" + typeAttribute + "'\r\nXML: " + node.OuterXml, error);
                    }
                }
                fileList.Add(newFile);
            }
            else
            {
                // Unknown sub-item
                throw new NetReflectorException(fileElement.Name + " is not a valid sub-item.\r\nXML: " + node.OuterXml);
            }
        }
    }
    return fileList.ToArray();
}

The arguments are an XmlNode and a NetReflectorTable. The node is the complete node to be de-serialised, in this case the <files> element and everything in it. The NetReflectorTable is a type custom to NetReflector – it basically contains a dictionary of all the available types that can be loaded. In this scenario we are not going to use it, but it is used in other serialisers (e.g. for hash-tables).

The majority of the work in this method is validation – since this will override the automatic behaviour we have to do everything ourselves! Since the end objective is to return an array of MergeFileInfo instances, I need to make sure the user isn’t trying anything that is not allowed.

The main working lines are instantiating a new MergeFileInfo, setting the file name from the text of the element and then loading the action from the attribute. If the attribute is not there, then it assumes a Merge action. Once this is done it is added to a List<MergeFileInfo>. This gets done for every child element, and then at the end it returns an array of MergeFileInfo items.

Writing an Element

The Read() was a fairly long method because it included lots of validation (plus it might need some more later one). In contrast the Write() method is very simple. Here is the code that I wrote for it:

public override void Write(XmlWriter writer, object target)
{
    var list = target as MergeFileInfo[];
    if (list != null)
    {
        writer.WriteStartElement(base.Attribute.Name);
        foreach (var file in list)
        {
            writer.WriteStartElement("file");
            writer.WriteAttributeString("action", file.MergeAction.ToString());
            writer.WriteString(file.FileName);
            writer.WriteEndElement();
        }
        writer.WriteEndElement();
    }
}

The writer is where the XML will be written to, the target is the item to be serialised.

The only check is that the target is actually an array of MergeFileInfo. Once this is verified, then is is a simple matter of iterating through the array and dumping all the values to XML.

Beyond the Ordinary – Serialisers

I should mention that once this was implemented, the rest of the modifications were very simple. Now the <merge> task has the ability to copy files, rather than just merge them. And not only that, but it is also backwards compatible with previous versions!

So, while this post started off talking about the <merge> task, the real focus is how we can extend NetReflector serialisation by using a custom serialiser and type factory. if you want to see how it is done, take a look at the MergeFileSerialiser, MergeFileSerialiserFactory and MergeFilesTask classes.

On a final note, I should mention this did not need any changes to NetReflector. This functionality has always been there, even though I have only just discovered it. As well as the new merge files, time-outs also use this functionality – take a look at the TimeoutSerializer and TimeoutSerializerFactory classes.

So hopefully this opens some more possibilities, happy coding…

Posted in CruiseControl.Net, NetReflector | Tagged: | Leave a Comment »

Adventures with NetReflector

Posted by Craig Sutherland on 14 January, 2009

Introducing an Old Project

When I started developing for CruiseControl.Net I came across an external library called Exortech.NetReflector. This library is responsible for the de-serialisation of the configuration within core (and the dashboard) was was originally developed by the same guy who originally wrote CruiseControl.Net – Owen Rogers.

In a nutshell NetReflector allows dynamic de-serialisation of XML. While the standard XML serialisation attributes in the CLR are pretty good they don’t allow for the scenario where the actual de-serialisation types are unknown at compilation time. NetReflector allows for this scenario.

Now, while NetReflector is very stable (there have been no modifications to it for a long time) and does its job very well, there were a couple of enhancements I wanted to add.

So to cut a long story short, I asked Owen for access to modify the project and he has given me the keys to the entire project (yes he made me an admin, even though I only asked for dev rights). So, every now and then I will also be posting on enhancements to NetReflector.

First Enhancements

My first enhancements for NetReflector are to make the validation of CruiseControl.Net configuration easier. Sometimes the failure messages from NetReflector are downright cryptic (Object reference not set – where and why?)

This can make writing or modifying configuration files harder, especially if someone is new to CruiseControl.Net. Additionally, trying to support some of these issues often means trying the same thing and trying to work out where the failure is (which for me is often trial-and-error).

Initially I’m modifying the following errors:

Error Enhanced Error
Invalid item when loading an array item. Will now tell the user which item from which array and include the faulty XML.
Null reference error when the type attribute isn’t set. Will now tell the user that NetReflector can’t handle the item. If a type attribute is required it will tell the user that the attribute is missing. The faulty XML is also included.
Attributes allowed on base types (e.g. strings). This will now throw an exception telling the user attributes are not allowed.
Sometimes hard to work out where missing elements should be. Now includes the parent element so the user can see where the element is missing from.

I’ll make a new build with these enhancements and add it to CC.Net sometime.

Posted in NetReflector | Tagged: | Leave a Comment »