![]() |
Languages »
XML »
Serializing
Intermediate
.NET XML and SOAP Serialization Samples, TipsBy gokselmProvides samples for XML and SOAP serialization using C#. |
C#, XML.NET 1.1, Win2K, WinXP, Win2003VS.NET2003, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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.
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.
IsNullable attribute should be set to true. For example: [ XmlElement( IsNullable = true ) ]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";
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>
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();
}
}
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;
}
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();
}
}
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.
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 do not cause any problem with XML serialization.
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.
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>
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.
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.
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. It�s 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();
}
}
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.
IDictionary objects which cannot be serialized using XML serialization can be serialized using SOAP/Binary serialization.
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();
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>
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;
}
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;
}
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;
}
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.
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.
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;
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 22 Jul 2005 Editor: Rinish Biju |
Copyright 2005 by gokselm Everything else Copyright © CodeProject, 1999-2009 Web19 | Advertise on the Code Project |