11th February 2007, updated:5th May 2008

xmlobjects - Xml mapping for C#

Description

xmlobjects is a lightweight library to map Xml documents to C# classes, whose main purpose is to simplify the handling of simple Xml files; its objective is not the serialization of objects in Xml (this functionality is built in .Net).

The mention of simple Xml files applies to two restrictions:

  • Size: the file is mapped to C# classes, all the required information maps to structures in memory.
  • Definition: currently, namespaces are not supported.

With xmlobjects, the programmer must create a hierarchy of classes that represents directly the Xml instances; my main objective is to facilitate the handling of configuration files, although it can obviously read any Xml files -and write them-.

License and download

xmlobjects is delivered as it, without any responsabilities on the author. It is open source, it can be used or modified without any limitations.

The library is only available in source format.

Usefulness of xmlobjects

In many cases -even most cases-, the support provided by xmlobjects is redundant, as quite an equivalent functionality is directly build in the .Net framework.

The Xml Serialization support in the framework -defined in the namespace System.Xml.Serialization- already provides a mapping between C# classes and Xml instances. And the System.Configuration namespace provides the required support for handling configuration files in client applications; explanations of these mechanisms can be found in the following URLs:

I wrote xmlobjects beacuse I needed a specific feature, not covered directly in the Xml serialization .Net support: knowing the specific order on which subelements are defined. I know that such functionality can be indirectly covered, adding extra information in the Xml file, or providing my custom reader/writer for the specific Xml class; however, modifying the schema is not always an option, and customizing the reader/writer already requires some effort. Obviously, such effort would always be lesser than writing the xmlobjects library, but having my own implementation could facilitate introducing further requirements.

Specific features supported in xmlobjects:

  • It can provide the order on which elements are defined.
  • It supports building a complete hierarchy of objects to represent the Xml instance, with parent and child relationships.
  • It supports List<T> fields; arrays of elements can be associated to basic arrays or generic lists.
  • For primitive types, mapped to Xml attributes, the library can provided extended knowledge on whether those attributes have been specified or not in the Xml file.
  • C# classes can define methods that are called when the reading or writing is performed.
  • It supports generic elements, arrays to store subelements not directly mapped to class attributes.
  • It supports easy configuration of the default behaviour.
  • Source is fully available, together with a complete set of unit tests.

Interface

The main objective of xmlobjects is the reading of simple files; the programmer defines one or more classes that map the Xml schema, and triggers the creation of C# instances by reading the Xml file. For example, a class with the following fields:

	public class Example {
		public string name;
		public Subname subs;
	}
	
	public class Subname {...}
has the Xml representation as:
	<Example name='...'>
		<Subname ... > ... </Subname>
		...
	</Example>

As with the System.Xml.Serialization namespace, xmlobjects rely on custom attributes to modify the default behaviour.

Any class can be mapped to a Xml type. By default, any public field is mapped to an attribute or a nested element of the Xml type. Note that this differs from the System.Xml.Serialization namespace, where public fields or properties can be mapped.

If the field's type is a primitive, an enumeration or a string, the mapping defaults to Xml attributes; in the rest of the cases, they map to Xml nested elements, as shown on the example above. This behaviour can be customized, for all the fields or for each individual field. The exception is the array fields, mapped always as attributes.

The library knows how to map automatically arrays, and has built-in support for List<T> elements, where T is not a List<T> itself. This means that having an ArrayList field, for example, will not have the expected result (replacing an array of objects)

Type attributes

A type can be decorated with the XmlType attribute, which allows the following modifiers:

  • AttributeFields:
    • By default is true, meaning that basic public fields associated to non-array types are mapped to Xml attributes. Setting it to true maps those fields to nested elements.
  • ChildsFields:
    • If specified, the given field will contain the references to all the child nodes, in the order found in the Xml file. This property is important when the order of the nested elements makes a difference.
  • ExplicitFields:
    • By default is false, meaning that all the public fields have Xml associations (unless a specific field includes the attribute XmlNoField). If it is set to true, the mapping fields must include the attribute XmlField.
  • GenericElementField:
    • If specified, the given field will contain the references to all the element not directly defined.
  • InnerTextField:
    • If specified, the given field will receive the inner text for a given Xml definition (if any). For example:
      <Subname>Dog</Subname>
      has Dog as inner text. If a Xml type has a given inner text and the type does not define an inner field, it is checked if the type defines a static Parse(string) method. If it does not, a Xml reading error is raised. Otherwise, the Parse method is invoked, but the type cannot then define any nested elements or attributes.
  • ParentField:
    • If specified, the given field (which must be public) will contain the reference to the parent node in the Xml file. This field will allow then full hierarchical navigation.
  • ProvidedFieldsSupport:
    • boolean value, related to the support of provided attributes or elements.
  • TypeAttribute:
  • XmlReadCompletedMethod:
    • If specified, the given method (no parameters), is called when the whole Xml has been parsed.
  • XmlWritePrepareMethod:
    • If specified, the given method (no parameters), is called when the Xml is to be written.
  • XmlWriteCompletedMethod:
    • If specified, the given method (no parameters), is called when the Xml writting is completed.

The XmlType attribute is not inherited, affects only to the declaring class. A subclass could define different settings for this attribute, but the redefined settings do not affect the parent classes.

For example, if the parent class defines the innerTextField, the subclass can define another innerTextField, and both fields will be set.

There is a limitation on the fields that can be specified in the type attribute: they can only refer to the declaring class. That is, a subclass cannot define its InnerTextField as a field defined above in its hierarchy.

Field attributes

The fields in a type can have two attributes: XmlNoField, meaning that the field has no Xml representation, and XmlField, which has the opposite meaning.

XmlField allows the following modifiers:

  • Name:
    • specifies the name of the associated attribute/element. If not specified, the Xml name is the variable name (case is not important)
  • AsElement:
    • A boolean value, it defines, for non-array types, whether the mapping is to an attribute or to a nested element.

The mapping is always case unsensitive. This implies that it is an error to define a class with two fields differing only in case.

Xml serialization

The library allows as well to save an instance as Xml, using the same specifications described for the loading. The objective of the library is to read / store Xml files, not to serialize the passed objects. In special, recursive definitions are not allowed.

Polymorphism support

The library supports polymorphism on the Xml elements. For example, in the following C# classes:

  
  class File { 
    public string name;
  }
  class Folder : File  {
    public File[] file;
  }
when a Folder is stored, if any of the referenced files is itself a folder, it will be stored as a folder (with subfiles). On this example, the stored Xml could look like:
  
  <File name='\'>
    <File name='.' />
    <File name='..' />
  </File>
That is, even when a File object is being stored, information on the fields defined it its real type (Folder), are used in the output (in this case, all the File subelements).

In some cases, this can be all that is needed, specially if the library is only used to save Xml files. However, if the produced file is read again by xmlobjects, using the same C# classes, there will be an error: a File is created with name '\', and then a subelement with name 'File' is required, but a File instance knows nothing about any 'File' field!

As mentioned above, xmlobjects does not intend to do objects serialization, but supporting this scenario makes perfect sense when the same polymorphism ideas are applied to the Xml definitions. In particular, this enables scenarios where the user or 3rd party libraries can extend predefined Xml (configuration or not) files.

To enable this support, the Xml element can include an optional element describing its real type, like in the following uncomplete example:

  <File name='\' type='Folder'>
    <File name='.' />
    <File name='..' />
  </File>
In this case, the 'type' attribute is used to specify the real type. It has no representation on the C# classes, there is no need to specify a field with that name (in fact, it would raise an error).

It is possible to specify the name of the type attribute in the XmlTypeAttribute associated to a class, or in a general way for all the classes. The following restrictions apply:

  • The type must be the same type (or subclass) of the expected type.
  • When storing the Xml objects, the type is only output if needed (if the type is different from the expected type)

The type attribute must include the full class type specification; and, if it is defined outside the assembly of the original type, the assembly is required as well. An example without assembly is:

<file name='aname' type='XmlObjectsTestUnits.TestB02TypeAssembly+NewFile>
And the following example shows the assembly information as well:
<file name='aname' type='XmlObjectsTestUnits.TestB02TypeAssembly+NewFile, 
NunitTests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'/>

Provided support

In some cases, it is needed to know when the Xml specifies one attribute or not; for example

  
  <given times='0'/>
can be indistinguable from
<given/>
when times is handled as an integer attribute.

If the class defines a boolean type named 'timesAttributeProvided', after the type 'times' is specified, and has no associated [XmlField] attribute, it is handled as a support field, that will contain true or false depending on whether the attribute is specified or not.

If the main type is an element, not an attribute, but is not an array, it can use the same functionality, but the support field must be named like in 'timesElementProvided'

Generating documentation

From version 1.10, it is possible to generate automatically HTML documentation from the XML schema.

The generation is done by an external program, this functionality is not implemented directly in xmlobjects, as different generation models could be used.

The project XmlObjectsDocGui performs this task.