Overview



Overview

XML Serialization is a misnomer: The term XML Mapping would be more accurate. Why? Simply put, XML Serialization is not designed to serialize any CLR object into XML. Rather, it is designed to map classes to schemas, and object instances of those classes to XML instances of those schemas. Handling all types of classes and objects is not a design goal of XML Serialization. In fact, most types found in the .NET Framework will not work with XML Serialization.

Instead, the goal is to represent any kind of XML, not any kind of object. This is a powerful and important distinction. The basis of XML Serialization is not the CLR type system; rather, it is the XSD Schema type system. XML Serialization provides a mapping between the two.

How does all of this work? Listing 5.1 contains a small example of XML Serialization.

An Example of XML Serialization
using System;
using System.IO;
using System.Xml.Serialization;

namespace XSDSample
{
     class Class1
     {
          [STAThread]
          static void Main(string[] args)
          {
               Test t = new Test();
               t.Foo = "Test";
               XmlSerializer ser = new XmlSerializer(typeof(Test));
               FileStream stream = new FileStream(
                                            "test.xml",
                                            FileMode.OpenOrCreate);
               ser.Serialize( stream, t );
          }
     }
     public class Test
     {
          public String Foo;
     }
}

The code in Listing 5.1 creates the following XML file:

<?xml version="1.0"?> 
<Test
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Foo>Test</Foo>
</Test>

As you can see, it's possible to read and write XML documents using the XmlSerializer class. Developers can then interact with and manipulate XML documents using classes as opposed to a stream-based parser or a DOM. I've seen 300 lines of XML code turn into 30 lines of code by switching from parsers to XML Serialization.

Internally, the XML Serialization infrastructure does several things when any application that uses it starts up. It is instructive to go through each step, to illustrate what is happening:

  1. An instance of the XmlSerializer class occurs, based on the type of the class.

  2. A mapping structure is created between this class and XML instances, based on the class structure and customizations from metadata attri butes applied to the class and its members.

  3. If this is the first instance, then a custom, stream-based reader and writer is created for this class that is designed purely to read and write XML into this class.

  4. When the Serialize (or Deserialize) methods are called, the XML instances are efficiently parsed in and out of the class instance passed to (or returned from) the method.

The following sections discuss the guidelines for mapping XML Schema into classes, and vice versa.

Only Classes with a Public, Default Constructor Will Be Serialized

The following snippet shows a class that contains a public and default constructor:

public class Test 
{
     public Test()
     {
     }
     public String Foo;
}

Now, if we add a constructor to this class that takes a string, and we don't explicitly add a default, then the class won't serialize. (The CLR will assume an implicit default constructor when no explicit constructors exist. When explicit constructors are added by user code, this assumption no longer exists.)

The following code shows a class with no public and default constructor:

public class Test 
{
     public Test( String foo )
     {
     }
     public String Foo;
}

Trying to serialize this results in the following error message:

System.InvalidOperationException: There was an error reflecting 
'XSDSample.Test'. ---> System.InvalidOperationException: XSDSample.Test cannot be serialized because it does not have a default public constructor.
   at System.Xml.Serialization.TypeScope.ImportTypeDesc(Type type, Boolean canBePrimitive, Boolean throwOnNoDefaultCtor, MemberInfo memberInfo) at System.Xml.Serialization.TypeScope.GetTypeDesc(Type type, MemberInfo source, Boolean throwOnDefaultCtor)
   at System.Xml.Serialization.ModelScope.GetTypeModel(Type type)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping (Type type, XmlRootAttribute root, String defaultNamespace) --- End of inner exception stack trace --- at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping (Type type, XmlRootAttribute root, String defaultNamespace) at System.Xml.Serialization.XmlSerializer..ctor(Type type)
   at XSDSample.Class1.Main(String[] args) in c:\documents and settings\administrator\my documents\visual studio projects\xsdsample\class1.cs:line 17

Only Public Fields and Properties Will Be Serialized

So that no unusual permissions are needed, such as the ability to reflect on private member variables, the XML Serializer serializes public fields and properties only.

The following class contains no public properties or fields that will be serialized:

public class Customer 
{
     private String Name;
     private String Address;
     private String PhoneNumber;
}

This class serializes without any internal data showing up, as in the following snippet:

<?xml version="1.0"?> 
<Customer
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
/>

However, if we add some public fields, then we can see that those serialize out.

public class Customer 
{
     public String Name;
     public String Address;
     public String PhoneNumber;
}

This class creates the following XML:

<?xml version="1.0"?> 
<Customer
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Name>Keith Ballinger</Name>
  <Address>1 Microsoft Way</Address>
  <PhoneNumber>(425)555-1212</PhoneNumber>
</Customer>

Read-Only Fields and Properties Will Not Be Serialized

Fields need to be both read and write in order to be serialized with XML Serialization. However, unlike the need for a default constructor, but like the public members requirement, the class will still serialize.

public class Customer 
{
     public String Name;
     public String Address;

     private String phoneNumber;

     public String PhoneNumber
     {
          get
          {
               return phoneNumber;
          }
     }
}

The class above only has read-only fields; therefore, those fields will not be turned into XML:

<?xml version="1.0"?> 
<Customer
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Name>Keith Ballinger</Name>
  <Address>1 Microsoft Way</Address>
</Customer>

Methods and Other Type Information Will Not Be Serialized

One thing I need to make as clear as possible is this: XML Serialization doesn't serialize any type information, including any information about methods your class may have. For example, the following snippet contains a method called CreateCustomer that will not appear in the XML:

public class Customer 
{
     public String Name;
     public String Address;
     public String PhoneNumber;

     public void CreateCustomer( String name, String address,
                                 String phoneNumber)
     {
          Name = name;
          address = Address;
          PhoneNumber = phoneNumber;
     }
}

No evidence of this CreateCustomer method appears in the resulting XML:

<?xml version="1.0"?> 
<Customer
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Name>Keith Ballinger</Name>
  <Address>1 Microsoft Way</Address>
  <PhoneNumber>(425)555-1212</PhoneNumber>
</Customer>

Furthermore, you can use one class to write out the XML, and an entirely different class to read in the XML, as shown in Listing 5.2.

Using Different Classes to Write and Read XML
class Class1
{
     [STAThread]
     static void Main(string[] args)
     {
          Customer t = new Customer();
          t.Name = "Keith Ballinger";
          t.Address = "1 Microsoft Way";
          t.PhoneNumber = "(425)555-1212";

          XmlSerializer ser = new XmlSerializer( typeof(Customer) );
          FileStream stream = new FileStream(
                              "cust.xml", FileMode.OpenOrCreate);
          ser.Serialize( stream, t );
          stream.Close();

     XmlSerializer ser2 = new XmlSerializer( typeof(CustomerType)
);
     FileStream inStream = new FileStream("cust.xml", FileMode.Open );
     CustomerType cust = (CustomerType)ser2.Deserialize( inStream );

          Console.ReadLine();
     }
}

[XmlRoot("customer")]
public class Customer
{
     public String Name;
     public String Address;
     public String PhoneNumber;
}
[XmlRoot("customer")]
public class CustomerType
{
     public String name;
     public String address;
     public String phone;
}

Type information is not preserved: The XML created from this doesn't contain any CLR-based type information. The XML Serializer is about schema fidelity, not type fidelity. Check out the binary serializer, which is part of .NET Remoting, for a serializer that is used to preserve type information.