Contents
This C# program demonstrates loading and saving an object containing a bitmap and collections to an XML file using .NET XML serialization. XML serialization enables an object's public fields and properties to be saved and loaded to/from an XML file.
This article focuses on the C# 2.0 (Visual Studio 2005) implementation. The C# 1.1 code does not use Generics and static class declaration, because those features are only available in C# 2.0.
After discovering the usefulness of XML in storing data (including configuration settings for a program), I needed a solution to load and save an object's public fields and properties to/from an XML file without the complexity of navigating through the DOM (Document Object Model). I also wanted to provide an abstraction of the XML data as class objects, and only concern myself with the XML specifics when saving or loading data to/from a file. In addition, this 'load/save to XML' framework had to be reusable in other projects.
A practical solution - and the one demonstrated here - is to use the functionality provided by the .NET System.Xml.Serialization.XmlSerializer
class encapsulated within a new class to provide loading and saving of an object to/from an XML file.
- Customer.cs - Class to be serialized to/from an XML file.
- EmailAddress.cs - Class that stores an e-mail address for the
Customer
and is used as the object type for a collection of e-mail addresses.
- ObjectXMLSerializer.cs - Class to encapsulate XML serialization, in XML document format (the default) or binary format.
- MainForm.cs - Windows Form class that demonstrates loading and saving of the
Customer
object to the 'CustomerObject.xml' file.
- CustomerObject.xml - File containing the serialized
Customer
object in the bin\Debug or \bin\Release folder (depending on your build configuration). This file is created when you press the Save button.
Before we can serialize an object to XML, the object's class code must include various custom metadata attributes (e.g., [XmlAttributeAttribute(DataType="date")]
) to tell the compiler that the class and its field and/or properties can be serialized. For example, in the Customer
class:
[XmlRootAttribute("Customer", Namespace="", IsNullable=false)]
public class Customer
{
private Bitmap picture;
public Customer()
{
}
[XmlAttributeAttribute(DataType="date")]
public System.DateTime DateTimeValue;
public int CustomerID;
public string CustomerName;
public int Age;
[XmlIgnoreAttribute()]
public bool CustomerPaid;
[XmlIgnoreAttribute()]
public Bitmap Picture
{
get { return picture; }
set { picture = value; }
}
[XmlElementAttribute("Picture")]
public byte[] PictureByteArray
{
get
{
if (picture != null)
{
TypeConverter BitmapConverter =
TypeDescriptor.GetConverter(picture.GetType());
return (byte[])
BitmapConverter.ConvertTo(picture, typeof(byte[]));
}
else
return null;
}
set
{
if (value != null)
picture = new Bitmap(new MemoryStream(value));
else
picture = null;
}
}
[XmlArray ("Hobbies"), XmlArrayItem("Hobby", typeof(string))]
public System.Collections.ArrayList Hobbies =
new System.Collections.ArrayList();
[XmlArray ("EmailAddresses"),
XmlArrayItem("EmailAddress", typeof(EmailAddress))]
public System.Collections.ArrayList EmailAddresses =
new System.Collections.ArrayList();
}
The ObjectXMLSerializer
class is used to encapsulate XML serialization, so that we can load and save an object regardless of its data type, with the help of Generics. Calling objects use the overloaded public methods, Load
and Save
.
The Load
methods deserialize XML data from a previously serialized XML file into an object's public fields and properties.
The Save
methods serialize an object's public fields and properties to an XML file.
Methods with the SerializedFormat
parameter allow setting of the serialization format - either XML document format, or binary format. If no serialization format is specified, XML document formatting is used, by default.
The following private methods within ObjectXMLSerializer
implement the core functionality for loading or saving data. Note that T
is the placeholder for the generic type used to represent a serializable object:
private static T LoadFromBinaryFormat(string path,
IsolatedStorageFile isolatedStorageFolder)
{
T serializableObject = null;
using (FileStream fileStream =
CreateFileStream(isolatedStorageFolder, path))
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
serializableObject = binaryFormatter.Deserialize(fileStream) as T;
}
return serializableObject;
}
private static T LoadFromDocumentFormat(System.Type[] extraTypes,
string path, IsolatedStorageFile isolatedStorageFolder)
{
T serializableObject = null;
using (TextReader textReader =
CreateTextReader(isolatedStorageFolder, path))
{
XmlSerializer xmlSerializer = CreateXmlSerializer(extraTypes);
serializableObject = xmlSerializer.Deserialize(textReader) as T;
}
return serializableObject;
}
private static void SaveToBinaryFormat(T serializableObject,
string path, IsolatedStorageFile isolatedStorageFolder)
{
using (FileStream fileStream =
CreateFileStream(isolatedStorageFolder, path))
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(fileStream, serializableObject);
}
}
private static void SaveToDocumentFormat(T serializableObject,
System.Type[] extraTypes, string path,
IsolatedStorageFile isolatedStorageFolder)
{
using (TextWriter textWriter =
CreateTextWriter(isolatedStorageFolder, path))
{
XmlSerializer xmlSerializer = CreateXmlSerializer(extraTypes);
xmlSerializer.Serialize(textWriter, serializableObject);
}
}
private static FileStream CreateFileStream(IsolatedStorageFile
isolatedStorageFolder, string path)
{
FileStream fileStream = null;
if (isolatedStorageFolder == null)
fileStream = new FileStream(path, FileMode.OpenOrCreate);
else
fileStream = new IsolatedStorageFileStream(path,
FileMode.OpenOrCreate, isolatedStorageFolder);
return fileStream;
}
private static TextReader CreateTextReader(IsolatedStorageFile
isolatedStorageFolder, string path)
{
TextReader textReader = null;
if (isolatedStorageFolder == null)
textReader = new StreamReader(path);
else
textReader = new StreamReader(new IsolatedStorageFileStream(path,
FileMode.Open, isolatedStorageFolder));
return textReader;
}
private static TextWriter CreateTextWriter(IsolatedStorageFile
isolatedStorageFolder, string path)
{
TextWriter textWriter = null;
if (isolatedStorageFolder == null)
textWriter = new StreamWriter(path);
else
textWriter = new StreamWriter(new IsolatedStorageFileStream(path,
FileMode.OpenOrCreate, isolatedStorageFolder));
return textWriter;
}
private static XmlSerializer CreateXmlSerializer(System.Type[] extraTypes)
{
Type ObjectType = typeof(T);
XmlSerializer xmlSerializer = null;
if (extraTypes != null)
xmlSerializer = new XmlSerializer(ObjectType, extraTypes);
else
xmlSerializer = new XmlSerializer(ObjectType);
return xmlSerializer;
}
The following example code shows how to use the ObjectXMLSerializer
class to save a Customer
object to an XML file (in document format). We first build a Customer
object from the values in the user interface, by calling our custom BuildCustomerFromForm
method:
Customer customer = this.CreateCustomer();
ObjectXMLSerializer<Customer>.Save(customer, XML_FILE_NAME);
The following example code shows how to use the ObjectXMLSerializer
class to load a Customer
object from an XML file (in document format):
customer = ObjectXMLSerializer<Customer>.Load(XML_FILE_NAME);
The Customer
class includes a Picture
property that stores a Bitmap
object. Because a Bitmap
cannot be automatically serialized/deserialized using the .NET System.Xml.Serialization.XmlSerializer
class, the Bitmap
object is saved to the XML file as an array of bytes, and converted back into a bitmap when the XML file is loaded.
Conversion from a Bitmap
object to a byte array is achieved using the Bitmap
object's System.ComponentModel.TypeConverter
. Conversion from a byte array back to a Bitmap
object is achieved using a System.IO.MemoryStream
object supplied to the Bitmap
object's constructor.
Note that two public properties are used to enable storing of the Bitmap
: Picture
and PictureByteArray
. The Picture
property is ignored during serialization (by using the XmlIgnoreAttribute
attribute) because it's of type Bitmap
. The value the Picture
property gets or sets is stored in XML, during serialization, using the PictureByteArray
property which performs the 'Bitmap
to byte
array' conversion (and vice-versa).
The Customer
class includes a Hobbies
field that stores an ArrayList
collection of strings. The attributes for the Hobbies
field are as follows:
[XmlArray ("Hobbies"), XmlArrayItem("Hobby", typeof(string))]
The XML attribute, XmlArray
, specifies the serialization of the Hobbies
field as an array of XML elements named "Hobbies
". The XML attribute, XmlArrayItem
, defines the name of the array item as it will be stored in the XML file, as "Hobby
", and its storage type as string
. The following example shows how the array of Hobbies
XML elements might appear in an XML file:
<Hobbies>
<Hobby>Golf</Hobby>
<Hobby>Tennis</Hobby>
<Hobby>Reading</Hobby>
</Hobbies>
The Customer
class includes an EmailAddresses
field that stores an ArrayList
collection of custom EmailAddress
objects. The attributes for the EmailAddresses
field are defined similar to the Hobbies
field.
Immediately before the class declaration, the XML attribute Serializable
specifies that the EmailAddress
class is serializable.
A default constructor with no parameters is included in the EmailAddress
class as it is required for the .NET serialization.
The XML attribute XmlAttribute
is specified for both fields of the EmailAddress
class to demonstrate the usage of XML attributes. The following example shows how the array of EmailAddresses
XML elements - with its fields stored as XML attributes - might appear in an XML file:
<EmailAddresses>
<EmailAddress Address="joebloggs@zenicom.com" Destination="Business" />
<EmailAddress Address="joebloggs@athome.com" Destination="Home" />
<EmailAddress Address="joebloggs@hotmail.com" Destination="Other" />
</EmailAddresses>
Original submission.
- Converted the solution to support .NET Framework 1.1.
- Modified the
ObjectXMLSerializer
class to allow binary formatting of XML.
- Added the
Picture
property to the Customer
class to demonstrate storing a Bitmap
object as XML.
- Modified
TestForm
to include the above changes.
- Added the "
Hobbies
" collection to demonstrate storing a collection of strings as XML.
- Added the "
EmailAddresses
" collection to demonstrate storing a collection of custom objects as XML.
- Changed the use of 'property' to 'field', where appropriate (previous versions incorrectly used 'property' and 'field' synonymously).
- Modified the C# project to include the above changes.
- Created a C# 2.0 implementation.
- Changed the
ObjectXMLSerializer
class declaration (C# 2.0 only) and members from instance to static, because no data or behavior in the class depends on object identity.
- Replaced the data type used as a serialized object with the use of generics, to provide strongly typed functionality (C# 2.0 only).
- Refactored comments for clarity.
- Corrected bugs in the commented examples.
- Added overloaded methods to provide alternative access.
- Changed the return type of
Save
methods to void
instead of bool
, because a file that cannot be saved will result in an exception.
- Changed the case of parameters to camelCase to match the .NET Framework Design Guidelines.
- Added
using
statements to objects that implement IDisposable
to simplify object disposal.
- Added additional private methods to encapsulate code.
- Removed extraneous object instantiation code for
Load
methods that remained after conversion from C# 1.1 to 2.0.
- Removed demo version downloads for simplicity.
- Renamed the
SerializedFormats
enum to SerializedFormat
to match the .NET Framework Design Guidelines.