Basic C# Xml Serialization

Working from a schema

A simple schema

<xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:domain="http://www.company.com/domain"
  targetNamespace="http://www.company.com/domain"
  elementFormDefault="unqualified"
  attributeFormDefault="unqualified">
 
   <xsd:element name="Person" type="domain:Person">
   </xsd:element>
 
   <xsd:complexType name="Person">
      <xsd:all>
         <xsd:element name="Name" type="xsd:string" nillable="false" minOccurs="1" maxOccurs="1"/>
         <xsd:element name="Address" type="domain:Address" nillable="false" />
         <xsd:element name="Sex" type="domain:Sex" nillable="false" />
         <xsd:element name ="Pets" minOccurs="0" maxOccurs="1">
            <xsd:complexType>
               <xsd:sequence>
                  <xsd:element name="Pet" type="xsd:string" minOccurs="1" maxOccurs="unbounded"/>
               </xsd:sequence>
            </xsd:complexType>
         </xsd:element>
      </xsd:all>
   </xsd:complexType>
 
   <xsd:complexType name="Address">
      <xsd:all>
         <xsd:element name="Street" type="xsd:string" nillable="false" />
         <xsd:element name="City" type="xsd:string" nillable="false" />
         <xsd:element name="Zip" type="xsd:string" nillable="false" />
         <xsd:element name="State" type="xsd:string" nillable="false" />
      </xsd:all>
   </xsd:complexType>
 
   <xsd:simpleType name="Sex">
      <xsd:restriction base="xsd:string">
         <xsd:enumeration value="Male" />
         <xsd:enumeration value="Female" />
      </xsd:restriction>
   </xsd:simpleType>
</xsd:schema>

This is just a very simple schema based on a person entity for demonstration purposes.

Code generation with XSD

Now we will use the xsd tool in mono to generate a binding class. This is sort of an optional step that will probably save you a few keystrokes and some sanity. It can be especially useful if you are unsure how to represent a schema construct in C#. If you are weaving the schema into a preconceived domain hierarchy this step will not buy you much. The code generator generates all the classes in one file and it generates elements as public fields, not properties. There does not seem to be a way to alter this besides maybe generating each type by itself. But for completeness we will show the process here.

F:\applications\Mono-2.6.1\bin\xsd schema.xsd /c /l:CS /o:./bound /n:com.company.bound
Written file ./bound\schema.cs

Fitting the existing model

Another problem with this is that the entity relationships are concrete types and not interfaces. Any good domain model contains entities that are defined with interfaces (in my opinion) which the schema and the xsd tool have no concept of. Luckily, the meta tags (annotations) in the System.Xml.Serialization package are complete enough to decorate away just about any domain marshalling situation you might have. Also the enumeration concept translates fairly transparently from xsdto C#. These types must be defined in the xsd and will be generated with the source but they will probably already exist in the domain model and be in the model interfaces. You can safely delete the generated ones and use the existing, just make sure the elements are the same. So we have to rearrange our class a bit:

A few things to notice here about what we did:

  • Changed all of the public fields to private fields and fronted them with public properties (more on returning interface types later).
  • Implemented existing interfaces for the types and made sure the newly exposed properties fulfilled the interface.
  • Changed the pet names to a List<string> instead of on string[] (more on indexed property types later).

‘To-many’ relationships

Out in the wild in many OO languages, (one-)to-many relationships are presented as IList<T> or some other typed collection interface. It is unfortunate that the C# XML serialization framework chose not to handle this pretty common case (and based on forum posts by Microsoft developers and evangelists, it will never be). If you noticed, the xsd tool generated the relationship as an array. The XmlSerializer is perfectly happy with you substituting a List<string> for a string[], but not an IList<string>. Since the indexed field that the marshaller uses is used only by the serialization framework (not exposed on the interface), it makes no difference anyway. Leaving it as an array is fine unless you are just that rigid about your style.
So the choices are:
Hold your nose and expose your to-many relationships as the implementation List<T> and hope it always fulfills your needs (and it usually will).
Expose the property the XmlSerializer expects and also implement the property the interface expects. They both point to the same underlying field.
Let’s talk about option two. This seems like a hack… well, it is but it’s not so bad. Here is how we are going to handle that here:

[XmlIgnore]
public IList<string> PetNames {
   get {
      return petNames;
   }
 
   set {
      petNames.Clear();
      petNames.AddRange(value);
   }
}
 
[XmlArray(ElementName="PetNames")]
[XmlArrayItem(ElementName = "Pet", IsNullable = false)]
public string[] _PetNames {
   get {
      return petNames.ToArray();
   }
 
   set {
      petNames.Clear();
      petNames.AddRange(value);
   }
}

So now we have two properties: one for the XmlSerializer and one for the C# API. As it turns out these can coexist nicely because if you think about it, the Person class will always be called an IPerson in C# so this property will never show itself as a possible code completion. But on the flip side we are not as fortunate. There as a public property called PetNames that the serializer would love to get its hands on. To prevent this we annotate it with the System.Xml.Serialization.XmlIgnore. Now we are set, IPerson knows nothing of _PetNames and the XML knows nothing of PetNames.

Supporting Code

Serialization

TextReader reader = new StreamReader(file);
XmlSerializer serializer = new XmlSerializer(typeof(my.package.MyType));
my.package.IMyType person = (my.package.IMyType)serializer.Deserialize(reader);
reader.Close();

Deserialization

IMyType instance = new MyType();
...
XmlSerializer serializer = new XmlSerializer(instance.GetType());
Stream stream = new FileStream(file, FileMode.Create,
FileAccess.Write, FileShare.None);
serializer.Serialize(stream, instance);
stream.Close();

Common XML patterns

These are implementation examples of documented, well know XML patterns using the .NET XML serialization framework. These are examples that may be not so obvious how to implement within the serialization framework and not a complete list. As I come across patterns that might not be so obvious to implement I will update this entry with them.

Collection tag

This pattern has already been used above in the original example.

Schema

<xsd:element name ="Pets" minOccurs="0" maxOccurs="1">
   <xsd:complexType>
      <xsd:sequence>
         <xsd:element name="Pet" type="xsd:string" minOccurs="1" maxOccurs="unbounded"/>
      </xsd:sequence>
   </xsd:complexType>
</xsd:element>

Code

[XmlIgnore]
public IList<string> PetNames {
   get {
      return petNames;
   }
 
   set {
      petNames.Clear();
      petNames.AddRange(value);
   }
}
 
[XmlArray(ElementName="PetNames")]
[XmlArrayItem(ElementName = "Pet", IsNullable = false)]
public string[] _PetNames {
   get {
      return petNames.ToArray();
   }
 
   set {
      petNames.Clear();
      petNames.AddRange(value);
   }
}

XML

<PetNames>
   <Pet>Fido</Pet>
   <Pet>Felix</Pet>
   <Pet>BoBo</Pet>
   <Pet>Tavi</Pet>
</PetNames>
Unless otherwise stated, the content of this page is licensed under GNU Free Documentation License.