Using the System.Xml.Serialization.XmlSerializer
The main purpose of this article is to discuss
some of the uses for Xml Serialization and explore some of the basics of using
the XmlSerializer
and the attributes in the System.Xml.Serialization
namespace. To keep this discussion basic, I will not talk about XML namespaces,
or the attributes' more specific constructors. This article only aims to give a
basic introduction to the serializer and attributes, and some links to further
reading and helpful tools. However, one of the great things about this approach
to working with XML, is that it takes very little work and you can benefit
greatly from the basics alone. While a full exploration of the API is
recommended, you can accomplish much with only a basic understanding. And with
the use of tools such as Skeleton Crew, you need only the most basic
understanding to put these techniques to use.
Depending on your needs, there are several options
available when working with XML. If you only need to read certain portions of
your XML, using XPath queries with XmlNode.SelectNodes()
and XmlNode.SelectSingleNode()
is a reasonable option.
But if you need to make your XML and code interchangeable, this is not a viable
approach. To move from one to the other, both the code model and the XML
structure must have the ability to represent the same information no matter how
it's formatted. For this you have three options... If you do not have a
specific XML structure that you need to adhere to, you can use the DataSet
's
ReadXml()
and WriteXml()
methods.
If you have a specific XML structure or an XML Schema that you need to
represent in code, you can use the XSD.exe tool, though it has some unpleasant
side effects. However, if you have a class hierarchy that you need to represent
in XML you're only two options are to use the the serialization attributes
and/or the XmlAttributeOverrides
class with the
XmlSerializer
. But keep in mind, that this last option can handle
all of the previously mentioned scenarios. Actually, the XSD.exe tool uses this
approach as well, it just generates the code from XML or XSD. The main problems
with the XSD tool are the lack of customizability and the ugly code it
produces.
Using the XmlSerializer & Serialization Attributes
In discussing the XmlSerializer
and the serializer attributes, we're talking about either modifying existing
code to specify how it's data will be serialized, or creating new code to
produce a specific XML structure. For the most part, this only entails
attaching attributes to the public fields or properties of classes, and in some
situations to the class itself. But before we dive into the attributes, let's
talk a little about the XmlSerializer
...
The XmlSerializer
Given XML, the XmlSerializer
can produce a graph of objects that hold the same instance data. This is known
as XML de-serialization. For this to work, the classes of the objects must be
structured to fit the XML supplied, or the attributes in the System.Xml.Serialization
namespace must be used to specify how the object's properties map to XML
entities.
The XmlSerializer
can also
produce XML when supplied with an instance of an Xml Serializable object. This
is known as XML Serialization. The criteria that determine if an object or
graph of objects are "Xml Serializable" are fairly simple...
-
In theory, it would be possible to serialize an interface, but impossible to
determine what concrete type to de-serialize to. Because of this, the
XmlSerializer
cannot work directly with an interface reference.
-
The
XmlSerializer
can only work with the public fields
on your types, and or the public properties that supply both get
and set
accessors.
-
The types of the properties of the type to be serialized must follow the
previous rules.
-
Xml Serialization exceptions are notoriously hard to read at first glance,
because they are almost always nested. Once you have a model built that you
intend to use with Xml serialization, it pays to use a pre-compiler to verify
the serializability of your classes. One such tool is listed at the end of this
article in the Links and Tools section.
The Attributes
With the use of the attributes in the System.Xml.Serialization
namespace, you can in effect make your classes completely interchangeable with
XML. The attributes basically allow you to specify if members of your class
should be an attribute or an element or an array, and what those XML entities
should be named in the XML.
XmlRootAttribute
The XmlRootAttribute
attribute
tells the serializer that the class that this attribute is attached to is the
document root node. It also allows you to specify what the root node is named
despite the class name. For example, the RootClass
class
is serialized to an XmlDocRoot
root node:
1
2
3
4
5
6
7
8
|
|
using System;
using System.Xml.Serialization;
namespace XmlEntities {
[XmlRoot("XmlDocRoot")]
public class RootClass {
}
}
|
Will serialize to something similar to this...
XmlElementAttribute
The XmlElementAttribute
attributes
allows you to specify that a member should be serialized as an element and what
the element should be named. Simple data (int
,
string
, etc...) and complex data (objects with fields or properties)
can be serialized to elements.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
|
using System;
using System.Xml.Serialization;
namespace XmlEntities {
[XmlRoot("XmlDocRoot")]
public class RootClass {
private string element_description;
[XmlElement("Description")]
public string Description {
get { return element_description; }
set { element_description = value; }
}
}
}
|
Will serialize to something similar to this...
1
2
3
|
|
<XmlDocRoot>
<Description>text</Description>
</XmlDocRoot>
|
XmlAttributeAttribute
The XmlAttributeAttribute
attribute
allows you to specify that a member should be serialized as an attribute and
what that attribute should be named. Only simple data can be used as an
attribute because an attribute can only represent a single value.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
|
using System;
using System.Xml.Serialization;
namespace XmlEntities {
[XmlRoot("XmlDocRoot")]
public class RootClass {
private int attribute_id;
[XmlAttribute("id")]
public int Id {
get { return attribute_id; }
set { attribute_id = value; }
}
}
}
|
Will serialize to something similar to this...
XmlTextAttribute
Using the XmlTextAttribute
attribute
specifies that the property it's attached to will be the text content of the
parent node. This attribute can only be attached to one property of the class
for obvious reasons.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
|
using System;
using System.Xml.Serialization;
namespace XmlEntities {
[XmlRoot("XmlDocRoot")]
public class RootClass {
private Description element_description;
[XmlElement("Description")]
public Description Description {
get { return element_description; }
set { element_description = value; }
}
}
public class Description {
private int attribute_id;
private string element_text;
[XmlAttribute("id")]
public int Id {
get { return attribute_id; }
set { attribute_id = value; }
}
[XmlText()]
public string Text {
get { return element_text; }
set { element_text = value; }
}
}
}
|
Will serialize to something similar to this...
1
2
3
|
|
<XmlDocRoot>
<Description id="1">text</Description>
</XmlDocRoot>
|
XmlIgnoreAttribute
Any public member that you do not wish to
serialize, or that cannot be serialized can be excluded from serialization by
using the XmlIgnoreAttribute
on the member. By using
this, you insure that the member will not be included in serialization or
de-serialization.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
|
using System;
using System.Xml.Serialization;
namespace XmlEntities {
[XmlRoot("XmlDocRoot")]
public class RootClass {
private System.IO.Stream stream;
[XmlIgnore()]
public System.IO.Stream Stream {
get { return stream; }
set { stream = value; }
}
}
}
|
XmlArrayAttribute & XmlArrayItemAttribute
When serializing an array or collection, without
the use of attributes, the default behaviour of the serializer is to append an
element per member of the collection. The XmlArrayAttribute
and XmlArrayItemAttribute
attributes allow you to alter
this behaviour. The XmlArrayItemAttribute
attribute
tells the serializer what the name and type of the elements of the collection
should be. The XmlArrayAttribute
specifies a parent
element for the collection elements.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
|
using System;
using System.Xml.Serialization;
namespace XmlEntities {
[XmlRoot("XmlDocRoot")]
public class RootClass {
private string[] element_list;
[XmlArrayItem("ListItem", typeof(string))]
[XmlArray("List")]
public string[] List {
get { return element_list; }
set { element_list = value; }
}
}
}
|
Will serialize to something similar to this...
1
2
3
4
5
6
|
|
<XmlDocRoot>
<List>
<ListItem>string</ListItem>
<ListItem>string</ListItem>
</List>
</XmlDocRoot>
|
XmlIncludeAttribute
The XmlIncludeAttribute
attribute
tells the serializer what types can be expected to extend a base type. When
serializing to XML, members of the type that this attribute is attached to will
be serialized based on the concrete type of the instance the property
references. When de-serializing from XML, the attributes give the serializer a
finite list of types that the instance data could fit. In the following
example, the XmlIncludeAttribute
lets the serializer
know that wherever an AbstractLogger is referenced that it could be an instance
of FileLogger, XmlLogger or DataLogger. Say we have a class that has a property
of type AbstractLogger... During de-serialization the serializer will attempt
to determine the type to de-serialize to based on the structure of the XML so
that it can instantiate a concrete instance for the property. While
serializing, if this property had an instance of FileLogger, the serializer
will use the FileLogger class to determine how to serialize that property. The
attributes just allow you to specify a finite list of the available types.
1
2
3
4
5
6
7
8
9
10
11
|
|
using System;
using System.Xml.Serialization;
namespace XmlEntities {
[XmlInclude(typeof(FileLogger))]
[XmlInclude(typeof(XmlLogger))]
[XmlInclude(typeof(DataLogger))]
public abstract class AbstractLogger : ILogger {
public abstract void Log(ILog log);
}
}
|
Using the XmlSerializer
Now we can talk about using the XmlSerializer
to convert from XML to objects and visa versa... The following example class
RootClassSerializer
is an example of how to use the XmlSerializer
class to read XML to objects and write objects to XML. This example was
generated using Skeleton Crew. A generic
implementation is easy enough to produce, but if you need to support XML
namespaces, the XmlSerializer
will need them explicitly
declared. In any event, the following is just an example of how to use the
XmlSerializer
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
|
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace XmlEntities {
public class RootClassSerializer {
private XmlSerializer s = null;
private Type type = null;
public RootClassSerializer() {
this.type = typeof(RootClass);
this.s = new XmlSerializer(this.type);
}
public RootClass Deserialize(string xml) {
TextReader reader = new StringReader(xml);
return Deserialize(reader);
}
public RootClass Deserialize(XmlDocument doc) {
TextReader reader = new StringReader(doc.OuterXml);
return Deserialize(reader);
}
public RootClass Deserialize(TextReader reader) {
RootClass o = (RootClass)s.Deserialize(reader);
reader.Close();
return o;
}
public XmlDocument Serialize(RootClass rootclass) {
string xml = StringSerialize(rootclass);
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.LoadXml(xml);
return doc;
}
private string StringSerialize(RootClass rootclass) {
TextWriter w = WriterSerialize(rootclass);
string xml = w.ToString();
w.Close();
return xml.Trim();
}
private TextWriter WriterSerialize(RootClass rootclass) {
TextWriter w = new StringWriter();
this.s = new XmlSerializer(this.type);
s.Serialize(w, rootclass);
w.Flush();
return w;
}
public static RootClass ReadFile(string file) {
RootClassSerializer serializer = new RootClassSerializer();
try {
string xml = string.Empty;
using (StreamReader reader = new StreamReader(file)) {
xml = reader.ReadToEnd();
reader.Close();
}
return serializer.Deserialize(xml);
} catch {}
return new RootClass();
}
public static bool WriteFile(string file, RootClass config) {
bool ok = false;
RootClassSerializer serializer = new RootClassSerializer();
try {
string xml = serializer.Serialize(config).OuterXml;
using (StreamWriter writer = new StreamWriter(file, false)) {
writer.Write(xml.Trim());
writer.Flush();
writer.Close();
}
ok = true;
} catch {}
return ok;
}
}
}
|
Conclusion
The thing to take away from this discussion, is
that using this approach to working with XML is extremely quick, easy and
effective. It takes very little work to setup existing models for
serialization, there are great tools for building models that are serializable,
and you don't have to write copious amounts of code to see if a node exists and
if it has data and if it has the right kind of data and if you can cast that
data to a usable type and blah... I'm getting a headache just thinking about
it. And if you're not working with XML, maybe you should start, it has a lot to
offer.
Links and Tools
-
Top XML
has an excellent article on the XmlSerializer.
-
Sells
Brothers XML Pre-Compiler is a good tool for testing the
serializability of a class or model that can tell you why a class isn't
serializable.
Tools that can help you write XML Attributes