Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

.NET XML and SOAP Serialization Samples, Tips

0.00/5 (No votes)
22 Jul 2005 1  
Provides samples for XML and SOAP serialization using C#

Introduction

Using serialization objects are persisted which allows us to store them easily in files or databases. E.g.: Storing configuration settings of an application, storing custom settings of a user for an application, saving the state of an application. Since we have persisted forms of objects, we can transfer them between applications. Deserialization is the opposite of serialization, which creates the object from its persisted form.

Objects are serialized into streams during serialization. FileStream can be stored to persist the objects in files and MemoryStream can be used to store the serialized form of the object in memory.

.NET supports XML, SOAP and Binary serializations.

XML Serialization

  • By using XML serialization, only public properties and fields can be serialized. If private members are to be serialized, other serialization methods should be used.
  • It requires a parameterless constructor. This constructor is called during deserialization.
  • Serialized form of the object does not contain any type, assembly information of the class. Only the data is stored.
  • Using XML serialization, not all the classes can be serialized. Classes that implement IDictionary cannot be serialized. E.g. Hashtables cannot be serialized. Arrays of objects can be easily serialized. Collections can be serialized but to serialize collections, certain rules must be followed.
  • Null values are not serialized with XML serialization. To include the null members in the serialized form, IsNullable attribute should be set to true. For example:
    [ XmlElement( IsNullable = true ) ]

Creating the Object

Let’s create our class for the serialization sample. Exam class contains a header and a set of questions:

public class Exam{
 public Exam(){
  header=new Header();
 }
 private Header header;
 private Question[] questions;
 public Header Header{
  get{return header;}
  set{header=value;}
 }
 public Question[] Questions{
  get{return questions ;}
  set{questions=value;}
 }
}
public class Header{
 public Header(){}
 private string title;
 public string Title{
  get{return title;}
  set{title=value;}
 }
}
public class Question{
 public Question(){}
 private int id;
 private string[] items;
 public int ID{
  get{return id;}
  set{id=value;}
 }
 public string[] Items{
  get{return items;}
  set{items=value;}
 }
}

And we initialize the object:

Exam exam=new Exam();
exam.Header=new Header();
exam.Header.Title="Exam title";
exam.Questions=new Question[1];
exam.Questions[0]=new Question();
exam.Questions[0].ID=1;
exam.Questions[0].Title=
    "What is your favourite serialization method?";
exam.Questions[0].Items=new string[2];
exam.Questions[0].Items[0]="Xml";
exam.Questions[0].Items[1]="Soap";

Serializing the Object into a File using XML Serialization

public static  void ToXml(Object objToXml,
               string filePath,bool includeNameSpace) { 
 StreamWriter stWriter=null; 
 XmlSerializer xmlSerializer; 
 try { 
  xmlSerializer = new XmlSerializer(objToXml.GetType()); 
  stWriter = new StreamWriter(filePath); 
  if (!includeNameSpace){
    
    System.Xml.Serialization.XmlSerializerNamespaces xs= 
                          new XmlSerializerNamespaces();
    //To remove namespace and any 
    //other inline information tag
    xs.Add("","");
    xmlSerializer.Serialize(stWriter, objToXml,xs); 
  }
  else{
    xmlSerializer.Serialize(stWriter, objToXml); 
  }
 } 
 catch(Exception exception){
  throw exception;
 }
 finally { 
  if(stWriter!=null) stWriter.Close(); 
 }    
}

Our method takes the file path to serialize the object. We pass the object to serialize and the method creates a file serializing the object. And the file content would be:

<?xml version="1.0" encoding="utf-8"?>
<Exam>
 <Header>
   <Title>Exam title</Title>
 </Header>
 <Questions>
  <Question>
   <ID>1</ID>
   <Title>What is your favourite serialization method?</Title>
   <Items>
     <string>Xml</string>
     <string>Soap</string>
   </Items>
  </Question>
 </Questions>
</Exam>

DeSerializing the Object from the XML File

The following method can be used to deserialize an object from a file:

public static object XmlToFromFile(string filePath,
                                        Type type) { 
 XmlSerializer xmlSerializer; 
 FileStream fileStream=null;
 try { 
  xmlSerializer = new XmlSerializer(type); 
  fileStream = new FileStream(filePath,
              FileMode.Open,FileAccess.Read); 
  object objectFromXml= 
       xmlSerializer.Deserialize(fileStream );    
  return objectFromXml;
 }    
 catch(Exception Ex) {
  throw Ex;
 }
 finally {     
  if(fileStream!=null) fileStream.Close(); 
 }    
}

Serializing the Object into a String in Memory using XML Serialization

Sometimes, we need to store the serialized form of an object in memory. We can use MemoryStream to store the serialized form of an object in memory.

public static string ToXml(Object objToXml,
                     bool includeNameSpace) { 
 StreamWriter stWriter=null; 
 XmlSerializer xmlSerializer; 
 string buffer; 
 try { 
  xmlSerializer = 
        new XmlSerializer(objToXml.GetType()); 
  MemoryStream memStream = new MemoryStream(); 
  stWriter = new StreamWriter(memStream); 
  if (!includeNameSpace){ 
  
    System.Xml.Serialization.XmlSerializerNamespaces xs= 
                         new XmlSerializerNamespaces();
                         
    //To remove namespace and any other inline 
    //information tag                      
    xs.Add("","");
    xmlSerializer.Serialize(stWriter, objToXml,xs);
  }
  else{
   xmlSerializer.Serialize(stWriter, objToXml); 
  }
  buffer = Encoding.ASCII.GetString(memStream.GetBuffer()); 
 } 
 catch(Exception Ex){
  throw Ex;
 }
 finally { 
  if(stWriter!=null) stWriter.Close(); 
 } 
 return buffer; 
}

DeSerializing the Object from an XML String in Memory

Of course, you will need a deserializer to convert in memory strings to objects!

public static object XmlTo(string xmlString,Type type) { 
 XmlSerializer xmlSerializer; 
 MemoryStream memStream=null;
 try { 
  xmlSerializer = new XmlSerializer(type); 
  byte[] bytes=new byte[xmlString.Length];
  
  Encoding.ASCII.GetBytes(xmlString,0,xmlString.Length,bytes,0);
  memStream = new MemoryStream(bytes);   
  object objectFromXml= xmlSerializer.Deserialize(memStream);    
  return objectFromXml;
 }    
 catch(Exception Ex) {
  throw Ex;
 }
 finally {     
  if(memStream!=null) memStream.Close(); 
 }    
}

Changing the Class

Adding New Properties

Let’s add a description property to the header class. The header class is shown below:

public class Header{
 public Header(){}
 private string title;
 private string description;
 public string Title{
  get{return title;}
  set{title=value;}
 }
 public string Description{
  get{return description;}
  set{description=value;}
 }  
}

We can still deserialize the objects without getting any errors. Values that do not exist in the serialized form are set to initial values. In this case, description property is set to null.

Removing a Property

Now, we want to remove the description property from the Header class. We want to deserialize an object from a file having the description property in the header section. Do we get any errors? We can deserialize successfully without any error! Removing or adding new properties does not cause any problem with XML serialization.

Controlling XML Serialization using Attributes

Serialization can be controlled using attributes. A property can be marked to be excluded from serialization or it can be serialized as an XML attribute or XML element. SOAP/Binary serialization attributes differ from XML serialization attributes. The sample uses some of the attributes to explain how to control serialization.

Changing the Text of a Property while Serializing

Suppose that you want to see “QuestionTitle” in the file that the object is serialized to instead of “Title” tag inside the question tags. But you want to refer to the same property in the code. To do that, we should set the new value using the XML attribute as shown below:

[System.Xml.Serialization.XmlElementAttribute("QuestionTitle")] 
public string Title{
 get{return title;}
 set{title=value;}
}

If you apply this attribute to an array property, it would cause the outer tag to disappear.

[System.Xml.Serialization.XmlElementAttribute("Item")]  
public string[] Items{
 get{return items;}
 set{items=value;}
}

Final output would be:

<?xml version="1.0" encoding="utf-8"?>
<Exam>
  <Header>
    <Title>Exam title</Title>
    <Description>Exam description</Description>
  </Header>
  <Questions>
   <Question>
     <ID>1</ID>
     <QuestionTitle>What is your favourite serialization method?
     </QuestionTitle>
     <Item>Xml</Item>
     <Item>Soap</Item>
   </Question>
  </Questions>
</Exam>
Adding an ArrayList to a Class

Let’s change the Exam class so that we can use ArrayList for questions instead of an array.

public class Exam{
 public Exam(){
  header=new Header();
  questions=new ArrayList();
 }
 private Header header;
 
 private ArrayList questions;
 public Header Header{
  get{return header;}
  set{header=value;}
 }
 public ArrayList Questions{
  get{return questions ;}
  set{questions=value;}
 }
}

When we run the sample, we get InvalidOperationException with “The type SerializationSamples.Question was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.” inner exception.

We can specify the type in the ArrayList using XmlArrayItem attribute. It allows us to be able to use ArrayLists while serializing the classes.

[XmlArrayItem(typeof(Question))]
public ArrayList Questions{
 get{return questions ;}
 set{questions=value;}
}

Now the serialization works as it was, generating the same output to the file.

Customizing the Serialization

Sometimes, we need to control XML serialization. In this case, we can override serialization to change its default behaviour. We create our own StreamWriter and use this class while serializing. For example, let’s remove the XML tag from our XML output. So we will not see “<?xml version="1.0" encoding="utf-8"?>” line in the output anymore.

Example: Removing XML Version Line

To control everything during serialization, ISerializable interface must be implemented instead of using .NET serialization.

Let’s write the class. It is called SpecialXmlWriter and it is inherited from XmlTextWriter. Its constructor takes a parameter to include the XML version line and WriteStartDocument is overridden:

public class SpecialXmlWriter:XmlTextWriter 
{
 bool m_includeStartDocument=true;
 public SpecialXmlWriter(TextWriter tw,
             bool includeStartDocument):base(tw) {
  m_includeStartDocument=includeStartDocument; 
 }  
 public SpecialXmlWriter(Stream sw,Encoding encoding,
              bool includeStartDocument):base(sw,null) {
  m_includeStartDocument=includeStartDocument;
 }
 public SpecialXmlWriter(string filePath,Encoding encoding,
           bool includeStartDocument):base(filePath,null) {
  m_includeStartDocument=includeStartDocument;
 }
 public override void WriteStartDocument() {
  if (m_includeStartDocument) {
   base.WriteStartDocument();
 }
}

Now let’s write another overloaded method for serialization. In this method, we used SpecialXmlWriter class instead of StreamWriter and passed the includeStartDocument parameter to the SpecialXmlWriter’s constructor.

public static void ToXml(Object objToXml,
                  string filePath,
                  bool includeNameSpace,
                  bool includeStartDocument) { 
 SpecialXmlWriter stWriter=null;
 XmlSerializer xmlSerializer; 
 try { 
  xmlSerializer = 
      new XmlSerializer(objToXml.GetType()); 
  stWriter = new SpecialXmlWriter(filePath,null,
                           includeStartDocument);
  System.Xml.Serialization.XmlSerializerNamespaces xs= 
                         new XmlSerializerNamespaces();
  
  //To remove namespace and any other 
  //inline information tag
  xs.Add("","");
  xmlSerializer.Serialize(stWriter, objToXml,xs); 
 } 
 catch(Exception Ex) {
  throw Ex;
 }
 finally { 
  if(stWriter!=null) stWriter.Close(); 
 }    
}

SOAP Serialization

  • During SOAP and binary serializatons, private fields, type information including assembly, namespace, public key token information are also serialized.
  • Deserialization gives the same object as it was before serialization.
  • Serialization can be controlled to exclude a field using [NonSerialized] attribute.
  • [Serializable] attribute is used to mark a class as serializable. It does not have to be put when XML serialization is used. When a class is marked with this attribute, runtime does all the work. However, it must be placed for each class that is to be serialized. Classes do not inherit this attribute from their base classes. To serialize a class, all the members of the class have to be serializable, otherwise it cannot be serialized.
  • To use SOAP and binary serialization, classes do not need to have a parameterless constructor. Because of performance issues, the constructor of the class is not called during deserialization.
  • IDictionary objects which cannot be serialized using XML serialization can be serialized using SOAP/Binary serialization.
    • Binary Serialization:
      • Compact serialization
      • Performs better
      • Creates byte stream
    • SOAP Serialization
      • Creates SOAP messages
      • Use if the serialization and deserialization platforms are not .NET
      • Use if the message is sent through a firewall
      • Easy debugging

Both SOAP and binary formatters inherit from IFormatter interface. The SOAP examples in this article can easily be changed to binary serialization samples by just changing the creation of formatter as shown below:

Formatter=new BinaryFormatter();

Serializing the Object into a File using SOAP Serialization

public static string ToSoap(Object objToSoap,
                             string filePath) {
 IFormatter formatter;
 FileStream fileStream=null;
 string strObject="";
 try{
  fileStream = new FileStream(filePath,
         FileMode.Create,FileAccess.Write);
  formatter = new SoapFormatter();
  formatter.Serialize(fileStream, objToSoap);
 }
 catch(Exception exception){
  throw exception;
 }
 finally{
  if (fileStream!=null) fileStream.Close();
 }
 return strObject;
}

Our method takes the file path and the object to serialize, and the file is created with the content shown below:

<SOAP-ENV:Envelope 

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 

  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" 

  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 

  xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" 

  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:Exam id="ref-1" 

  xmlns:a1="http://schemas.microsoft.com/clr/nsassem/
   SerializationSamples.SOAP/SerializationSamples%2C%20Version%3D1.0.0.0
   %2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<header href="#ref-3"/>
<questions href="#ref-4"/>
</a1:Exam>
<a1:Header id="ref-3" 

  xmlns:a1="http://schemas.microsoft.com/clr/nsassem/
    SerializationSamples.SOAP/SerializationSamples%2C%20Version
    %3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<title id="ref-5">Exam title</title>
<description id="ref-6">Exam description</description>
</a1:Header>
<a2:ArrayList id="ref-4" 

   xmlns:a2="http://schemas.microsoft.com/clr/ns/System.Collections">
<_items href="#ref-7"/>
<_size>1</_size>
<_version>1</_version>
</a2:ArrayList>
<SOAP-ENC:Array id="ref-7" SOAP-ENC:arrayType="xsd:anyType[16]">
<item href="#ref-8"/>
</SOAP-ENC:Array>
<a4:Question id="ref-8" 

    xmlns:a4="http://schemas.microsoft.com/clr/nsassem/
      SerializationSamples/SerializationSamples%2C%20Version
      %3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<id>1</id>
<title id="ref-9">What is your favourite serialization method?</title>
<items href="#ref-10"/>
</a4:Question>
<SOAP-ENC:Array id="ref-10" SOAP-ENC:arrayType="xsd:string[2]">
<item id="ref-11">Xml</item>
<item id="ref-12">Soap</item>
</SOAP-ENC:Array>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

DeSerializing the Object from the SOAP File

The following method can be used to deserialize an object from a SOAP file:

public static object SoapToFromFile(string filePath) {
 IFormatter formatter;
 FileStream fileStream=null;
 Object objectFromSoap=null;
 try{
  fileStream = new FileStream(filePath,
                FileMode.Open,FileAccess.Read);    
  formatter = new SoapFormatter();
  objectFromSoap=formatter.Deserialize(fileStream);
 }
 catch(Exception exception){
  throw exception;
 }
 finally{
  if (fileStream!=null) fileStream.Close();
 }
 return objectFromSoap;
}

Serializing the Object into a String in Memory using SOAP Serialization

public static string  ToSoap(Object objToSoap) {
 IFormatter formatter;
 MemoryStream memStream=null;
 string strObject="";
 try{
  memStream = new MemoryStream();
  formatter = new SoapFormatter();
  formatter.Serialize(memStream, objToSoap);
  strObject = 
     Encoding.ASCII.GetString(memStream.GetBuffer()); 
  
  //Check for the null terminator character
  int index=strObject.IndexOf("\0");
  if (index>0){
   strObject=strObject.Substring(0,index);
  }
 }
 catch(Exception exception){
  throw exception;
 }
 finally{
  if (memStream!=null) memStream.Close();
 }
 return strObject;
}

DeSerializing the Object from a SOAP String in Memory

public static object SoapTo(string soapString) {
 IFormatter formatter;
 MemoryStream memStream=null;
 Object objectFromSoap=null;
 try{
  byte[] bytes=new byte[soapString.Length];
  
  Encoding.ASCII.GetBytes( soapString, 0, 
               soapString.Length, bytes, 0);
  memStream = new MemoryStream(bytes);
  formatter = new SoapFormatter();
  objectFromSoap= 
       formatter.Deserialize(memStream);
 }
 catch(Exception exception){
  throw exception;
 }
 finally{
  if (memStream!=null) memStream.Close();
 }
 return objectFromSoap;
}

Making Changes in the Class before Deserializing

Adding a new property or changing the namespace does not effect the XML serialization. But if SOAP/Binary serialization is used, you should be very careful. SOAP/Binary serialization directly deals with members and also serializes the type information. In .NET, type information also includes the assembly name and namespace. Changing one of these may impact the application. Adding a new member or updating the members may cause problems. It may not be possible to deserialize the object back.

Adding a Property

Let’s add a new property with the type of string to our Exam class and try to deserialize the object from a file that is serialized with the previous version of the class. We have added Author property. Our Exam class becomes as follows:

[Serializable]
public class Exam{
 public Exam(){
  header=new Header();
  questions=new ArrayList();
 }
 private Header header;
 private ArrayList questions;
 private string author;
 public Header Header{
  get{return header;}
  set{header=value;}
 }
 public ArrayList Questions{
  get{return questions ;}
  set{questions=value;}
 }
 public string Author{
  get{return author ;}
  set{author=value;}
 }
}

When we run the application, we get the following exception "An unhandled exception of type 'System.Runtime.Serialization.SerializationException' occurred in SerializationSamples.exe".

Additional information: "Wrong number of Members. Object SerializationSamples.SOAP.Exam has 3 members, number of members deserialized is 2".

SOAP serialization serializes all members of the class and looks for all of them while serializing.

To overcome this problem, let’s start from the beginning. We remove the Author property to start from scratch. We add a Hashtable named “properties” for future enhancements to our class regardless of whether we use it or not. Currently we are not using this Hashtable but it will be serialized as an empty Hashtable. In future, if we want to add a new property, we will be storing and getting them from the hashtable. Since the Hashtable is already serialized, newly added properties can successfully be deserialized and serialized again. If the property is not stored inside the Hashtable, it can be set to a default value.

Our class becomes as follows:

[Serializable]
public class Exam{
 public Exam(){
  header=new Header();
  questions=new ArrayList();
 }
 private Header header;
 private ArrayList questions;
 private Hashtable properties;
 public Header Header{
  get{return header;}
  set{header=value;}
 }
 public ArrayList Questions{
  get{return questions ;}
  set{questions=value;}
 }
}

Now, add the Hashtable to the Exam class, initialize it and serialize the class, so that we can test the next step to understand if we can still add a new property.

public Exam(){
 header=new Header();
 questions=new ArrayList();
 properties=new Hashtable();
}
private Hashtable properties;

Let’s add the Author property and try to deserialize the object from an existing file. Author property uses the Hashtable to store and get its value. It does not use a class member, otherwise deserialization would break.

public string Author{
 get{
  if (properties[PROPERTYNAME.AUTHOR]==null){
   return "";
  }
  else{
   return (string)properties[PROPERTYNAME.AUTHOR];
  }
 }
 set{
  properties[PROPERTYNAME.AUTHOR]=value;
 }
}

And the PROPERTYNAME enumerations:

public enum PROPERTYNAME{
 AUTHOR=0
}

While adding a new property to the class, we add a new enum to the PROPERTYNAME enumeration and use this value as a key to get and store values inside the Hashtable.

Changing the Type Information

What happens if we want to make a change in the type information of our class. Type information is also serialized while the class is serialized enabling the class to be deserialized using the type information. Type information consists of namespace, class name, assembly name, culture information, assembly version, and public key token. As long as your deserialized class and the class that is serialized reside in the same assembly, it does not cause any problem. But if the serializer is in a separate assembly, .NET cannot find your class’ type hence cannot deserialize it.

If you change the assembly name of your class then you get the following error that tells you .NET cannot find the associated assembly "An unhandled exception of type 'System.Runtime.Serialization.SerializationException' occurred in serializationsamplesa.dll".

Additional information: "Parse Error, no assembly associated with XML key a1:http://schemas.microsoft.com/clr/nsassem/SerializationSamples.SOAP/ SerializationSamples%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull Exam".

If you change the namespace of your class, then you will get the following error that tells you .NET cannot find the associated type "An unhandled exception of type 'System.Runtime.Serialization.SerializationException' occurred in serializationsamples.dll".

Additional information: "Parse Error, no type associated with Xml key a1 SerializationSamples.SOAP.Exam SerializationSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null".

If it is very likely to make changes it might be a good idea to implement ISerializable interface instead of using the Serializable attribute. If the class implements ISerializable interface, all the serialization must be coded manually.

In this case, we can write our own class’s binder and tell the deserialization to use our binder while looking for our classes’ types. For each class that is serialized or type information recorded in the SOAP message, we need to return its type information so that the correct type can be loaded. In the last else statement in the code below, we return the default coming type information. In our case, they are Object, ArrayList and Hashtable types since these are the system classes that are used by our class.

public class ExamBinder: 
    System.Runtime.Serialization.SerializationBinder {
 
 public override Type BindToType(string assemblyName, 
                                    string typeName) { 
   string[] typeInfo=typeName.Split('.');
 
   //The latest item is the class name
   string className=typeInfo[typeInfo.Length -1];
   if (className.Equals("Exam")){
     return typeof (Exam);
   }
   else if (className.Equals("Header")){
     return typeof (Header);
   }
   if (className.Equals("Question")){
     return typeof (Question);
   }
   else{
     return Type.GetType(string.Format( "{0}, {1}", 
                        typeName, assemblyName));
   }
 } 
}

Now let’s create another overloaded method for deserialization. It takes a binder parameter and passes this parameter to the formatter’s binder property.

public static object SoapToFromFile(string filePath, 
                         SerializationBinder binder) {
 IFormatter formatter;
 FileStream fileStream=null;
 Object objectFromSoap=null;
 try{
   fileStream = new FileStream(filePath, 
             FileMode.Open, FileAccess.Read);    
   formatter = new SoapFormatter();
   formatter.Binder=binder;   
   objectFromSoap=formatter.Deserialize(fileStream);
 }
 catch(Exception exception){
   throw exception;
 }
 finally{
   if (fileStream!=null) fileStream.Close();
 }
 return objectFromSoap;
}

It can be called as shown below:

System.Runtime.Serialization.SerializationBinder binder=
                                        new ExamBinder();
Exam soapExamFromBinder = 
   XmlUtil.SoapToFromFile(txtFilePath.Text,binder) as Exam;

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here