Tuesday, May 26, 2009

Serialization in .NET - 2

In my previous blog we saw the basics of serialization and how types/objects can be serialized to Binary format. In this blog we will see how to serialize objects to XML.

Serializing objects to XML

Before we see the code to serialize objects to XML some things to keep in mind for serializing objects to XML is that the class must have a default constructor. If the class doesn’t have a default constructor then at runtime the following error will be thrown.

[So and So Class] cannot be serialized because it does not have a parameterless constructor.

Another point to keep in mind is that all public properties which needs to be serialized must have get and set methods implemented, else the following errors, when the property is read only or write only, will be thrown by the compiler at runtime.

InvalidOperationException with the following message will be thrown during serialization by the compiler if the property is declared read only (no set method defined) only.
Unable to generate a temporary class (result=1).error CS0200: Property or indexer 'Cars.Car.Color' cannot be assigned to -- it is read only

InvalidOperationException with a NullReferenceException as the inner exception will be thrown during serialization by the compiler if the property is write only (no get method defined) only.
InvalidOperationException message - "There was an error reflecting type 'Cars.Car'."
NullReferenceException message - "Object reference not set to an instance of an object."

The same Car class illustrated in the previous blog can be serialized into XML with the help of the below code.

using (System.IO.FileStream xmlFS = new System.IO.FileStream("car.xml", System.IO.FileMode.Create))
{
    Cars.Car car = new Cars.Car { Model = "BMW 7 Series", Color = "Red", NoOfDoors = 4 };
    System.Xml.Serialization.XmlSerializer xmlSer = new System.Xml.Serialization.XmlSerializer(car.GetType());
    xmlSer.Serialize(xmlFS, car);
}

One thing to note here is that unlike Binary serialization only public properties and fields will be serialized. Private member variables will not be serialized. The serialized xml of the Car class is pasted below.

<?xml version="1.0"?>
<Car xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Color>Red</Color>
  <Model>BMW 7 Series</Model>
  <NoOfDoors>4</NoOfDoors>
  <Price>0</Price>
</Car>

One more thing to note with XML serialization is that the class which needs to be serialized need not have a Serializable attribute applied to it like the one we used in binary serialization. To control XML serialization one needs to use attributes which control xml serialization. Some attributes which I think may be of use are listed below.

XmlIgnore

If you want a public field or property not to be serialized then you can make use XmlIgnore attribute. This will prevent a particular public field or property from being serialized. The serialized XML and XmlIgnore attribute applied to Color property of the Car class is pasted below.

namespace Cars
{   
    public class Car
    {       
        private string cubicCentimeter;
        [System.Xml.Serialization.XmlIgnore]
        public string Color
        { get; set; }
        public string Model
        { get; set; }
        public int NoOfDoors
        { get; set; }       
        public int Price
        { get; set; }    
    }
}

Serialized XML output after applying the XmlIgnore attribute.

<?xml version="1.0"?>
<Car xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Model>BMW 7 Series</Model>
  <NoOfDoors>4</NoOfDoors>
  <Price>0</Price>
</Car>

From the above xml one can see that the xml is missing the Color node as the XmlIgnore attribute is applied on it.

XmlAttribute

With the XmlAttribute attribute you tell the serialization process to convert the property or field as a XML attribute rather than as a XML node which is the normal behavior. If you want to change the attribute name and don’t want it to be same as that of the property name, you can make use of the “AttributeName” property along with the XmlAttribute attribute. To provide a namespace to the xml attribute you can make use of the “Namespace” property. When you provide namespace it has abide by w3 guidelines. As a general practice we provide urls as namespace. If you want to provide the data type of the attribute again that can be specified with the help DataType property. Sample code is pasted below.

public class Car
{       
    private string cubicCentimeter;
    [System.Xml.Serialization.XmlIgnore]
    public string Color
    { get; set; }  
   [System.Xml.Serialization.XmlAttribute (AttributeName="CarModel")]
    public string Model
    { get; set; }
    public int NoOfDoors
    { get; set; }       
    public int Price
    { get; set; }    
}

Serialized Xml output.

<?xml version="1.0"?>
<Car xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" CarModel="BMW 7 Series">
  <NoOfDoors>4</NoOfDoors>
  <Price>0</Price>
</Car>

From the above xml you can see with the XmlAttribute added the Model property of the Car class has been converted as an Xml Attribute (highlighted in maroon) with the attribute name as “CarModel”. The attribute name is not same as that of the property name the reason being we have specified a different name by making use of AttributeName property of the XmlAttribute attribute.

XmlElement

XmlElement can be used to control the xml element attributes. Using the XmlElement attribute on a public property/field will convert the property/field to a XML element. The properties which can be used with XmlElement are, ElementName, DataType, Form, IsNullable, Namespace, Order and Type.

With ElementName one can provide a Name to the element if you don’t want the XML element name to be same as that of the property/field name.

DataType will help you to specify the XML schema data type. Namespace will help you to provide namespaces to the XML element.

IsNullable specifies whether a XML element node should be generated if the property/field is null. If the IsNullable value is true an empty XML element with xsi:nil attribute set to true will be generated for properties/fields having null value else no XML element node will be generated. One thing to note here is that IsNullable cannot be used with value type properties/fields as value types cannot contain null as a value. If used along with value type the compiler will throw System.InvalidOperationException with the following message.

IsNullable may not be 'true' for value type System.BlahBlah.  Please consider using Nullable<System.BlahBlah> instead.

Form property of XmlElement will tell serializer to whether qualify the XML element with a namespace or not. The accepted values are System.Xml.Schema.XmlSchemaForm.None, System.Xml.Schema.XmlSchemaForm.Qualified and System.Xml.Schema.XmlSchemaForm.Unqualified. It is an error to use “Unqualified” value along with Namespace property in an XmlElement attribute i.e. one cannot use both Namespace and “System.Xml.Schema.XmlSchemaForm.Unqualified” as value for Form property,  If used the compiler will throw System.InvalidOperationException with the following error message.

//Form property wrongly used along with Namespace property
[System.Xml.Serialization.XmlElement(Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Namespace = "http://sandblogaspnet.com")]
public string Model
{ get; set; }

Error thrown as part of the above mistake.
The Form property may not be 'Unqualified' when an explicit Namespace property is present.

Order property in XmlElement will allow you to tell the serializer the order in which the XML element have to be serialized. If Order is used with any one property/field it has to be used with all the properties/fields. If not used with all the properties/field the compiler will throw a “System.InvalidOperationException” with the following Inner exception message.

“Inconsistent sequencing: if used on one of the class's members, the 'Order' property is required on all particle-like members, please explicitly set 'Order' using XmlElement, XmlAnyElement or XmlArray custom attribute on class member ‘XYZ’.”

With Type you can specifies the type of the object. Sample code showing most of the properties of the XmlElement attribute are shown below.

namespace Cars
{   
    public class Car
    {       
        private string cubicCentimeter; 
       [System.Xml.Serialization.XmlElement(Order = 2, IsNullable=true)]
        public string Color
        { get; set; } 
       [System.Xml.Serialization.XmlElement(ElementName = "ModelName", Order=1, IsNullable=true, DataType = "string")]
        public string Model
        { get; set; } 
       [System.Xml.Serialization.XmlElement(Order = 3, Namespace = "http://sandblogaspnet.com", Form = System.Xml.Schema.XmlSchemaForm.Qualified)]
        public int NoOfDoors
        { get; set; }
        [System.Xml.Serialization.XmlElement(Order = 4)]
        public int Price
        { get; set; }    
    }
}

//Car Class Initialization code.

Cars.Car car = new Cars.Car { Model = "BMW 7 Series", NoOfDoors = 4 };

Above Car class’ Serialized xml output.

<?xml version="1.0"?>
<Car xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <ModelName>BMW 7 Series</ModelName>
  <Color xsi:nil="true" />
  <NoOfDoors xmlns="http://sandblogaspnet.com">4</NoOfDoors>
  <Price>0</Price>
</Car>

One can see the serialized XML output of the Car class. An empty Color node with the xsi:nill attribute set to true because of the IsNullable attribute being set to true. Also notice the property Model being serialized as “ModelName” node because we have provided “ModelName” as the element name in the ElementName property of XmlElement attribute. Also you can notice an attribute called “xmlns” in ‘NoOfDoors” node added because of the Namespace property being used along with XmlElement attribute.

XmlRoot

XmlRoot attribute is used against a class, struct, enum etc to declare it as the root element of the Xml. The properties which can be used along with XmlRoot attribute are ElementName, DataType, IsNullable and Namespace. All these properties have been explained above. Sample code with the XmlRoot attribute applied along with the serialized XML is pasted below.

namespace Cars
{    
[System.Xml.Serialization.XmlRoot(ElementName="Car",
DataType="Cars.Car", IsNullable=true,
Namespace="http://Cars.Car")]
    public class Car
    {       
        private string cubicCentimeter; 
        [System.Xml.Serialization.XmlElement(Order =
2, IsNullable=true)]
        public string Color
        { get; set; } 
    [System.Xml.Serialization.XmlElement(ElementName
= "ModelName", Order=1, IsNullable=true, DataType =
"string")]
        public string Model
        { get; set; } 
      [System.Xml.Serialization.XmlElement(Order =
3, Namespace = "http://sandblogaspnet.com", Form =
System.Xml.Schema.XmlSchemaForm.Qualified)]
        public int NoOfDoors
        { get; set; }
        [System.Xml.Serialization.XmlElement(Order =
4)]
        public int Price
        { get; set; }    
    }
}

Serialized XML output for the above class.
<?xml version="1.0"?>
<Car xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance
" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://Cars.Car">
  <ModelName>BMW 7 Series</ModelName>
  <Color xsi:nil="true" />
  <NoOfDoors xmlns="http://sandblogaspnet.com">4</NoOfDoors>
  <Price>0</Price>
</Car>

XmlArray

If you want to control the serialization of collections which implement IEnumerable interface you can use XmlArray attribute. The properties which can be used along with XmlArray are Namespace, ElementName, Form, IsNullable and Order. The explanation for each of these have been already discussed in this blog so we will skip that part.

XmlArrayItem

XmlArrayItem can be applied to any class which implements IEnumerable interface. The attribute is used to describe the individual members of the collection/array. For e.g. you have an ArrayList collection object in which you want to store objects derived from the Car class then you have to tell the serializer about the composition of the ArrayList collection using the XmlArrayItem attribute. For e.g. the Car class is inherited by another class named Ferrari then you have to explicitly tell the compiler that the collection can contain Car object as well as Ferrari object as shown below.

public class CarCollection
{
    [System.Xml.Serialization.XmlArrayItem(ElementName = "Car", Type = typeof(Car)), System.Xml.Serialization.XmlArrayItem(ElementName = "Ferrari", Type = typeof(Ferrari))]
        public System.Collections.ArrayList Cars
        { get; set; }
}

If the XmlArrayItem is not used then the following error will be thrown by the compiler while serializing the collection.

System.InvalidOperationException - “There was an error generating the XML document.”

Inner Exception: “System.InvalidoperationException” - “The type Cars.Ferrari was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.”

One thing to note here is that if you are storing multiple types in a collection you have to specify all the types using the XmlArrayItem attribute. Even each derived classes have to be specified, just by specifying the parent class will not help. If you specify only the parent class then you can store only the parent class, trying to store the derived classes will throw the above error. So each an every derived class which you are planning to add in the collection needs to be specified. If any user type is missed then you will get the above error. Also if you are using the collection object to store .NET primitive types then there is no need to make use of the XmlArrayItem attribute. If you are making use of XmlArrayItem attribute to describe the primitive types in the collection then only the primitive types mentioned using the XmlArrayItem can be stored. Attempting to store primitive types other than the ones specified in the XmlArrayItem will throw the following error.

System.InvalidOperationException - "There was an error generating the XML document."

Inner Exception: System.InvalidOperationException - "The type System.Double may not be used in this context."

The above error was thrown because I haven’t mentioned Double as one of the XmlArrayItem. Code is pasted below.

[System.Xml.Serialization.XmlArrayItem(Type = typeof(string)), System.Xml.Serialization.XmlArrayItem(Type = typeof(int))]
public System.Collections.ArrayList PrimitiveTypes
{ get; set; }

So when the “PrimitiveTypes” property having a double value was serialized the above exception was thrown. So if your planning to store primitive types in a collection then its better not to decorate the property using the XmlArrayItem attribute. If you do then make sure you specify all the possible data types.

XmlInclude

XmlInclude can be used with classes, stucts, methods, web methods and interfaces. Its somewhat similar to XmlArrayItem in that XmlInclude can be used to describe the derived classes which needs to be included in the class, struct, method or interface. The same CarCollection is modified with the XmlInclude attribute and the sample code is pasted below.

[System.Xml.Serialization.XmlInclude(typeof(Car)),
System.Xml.Serialization.XmlInclude(typeof(Ferrari)),
System.Xml.Serialization.XmlInclude(typeof(FerrariF500))]
public class CarCollection
{
    public System.Collections.ArrayList Cars
    { get; set; }
}

If all the derived classes are not specified in the XmlInclude attribute then the same errors will be thrown as in XmlArrayItem.

XmlEnum

Can be used to describe how Enum needs to be serialized. It has only one property named Name using which you can specify what should be the name of the particular Enum value. Sample code is pasted below.

public enum CarType
{
    [System.Xml.Serialization.XmlEnum(Name="CompactCar")]
    SmallCar,
    [System.Xml.Serialization.XmlEnum(Name = "CompactSedan")]
    CS,
    Sedan,
    SportsCar,
    Suv
}

The Car class with the CarType enum added as a property  and the serialized content of the car class is pasted below.

namespace Cars
{      
    [System.Xml.Serialization.XmlRoot(ElementName="Car", IsNullable=true )]
    public class Car
    {         
        System.Xml.Serialization.XmlElement(Order = 2, IsNullable=true)]
        public string Color
        { get; set; }
        [System.Xml.Serialization.XmlElement(ElementName = "ModelName", Order=1, IsNullable=true)]       
        public string Model
        { get; set; }
        [System.Xml.Serialization.XmlElement(Order = 3)]
        public int NoOfDoors
        { get; set; }       
        [System.Xml.Serialization.XmlElement(Order = 4)]
        public int Price
        {
            { get; set; }
        }       
        [System.Xml.Serialization.XmlAttribute(AttributeName="CarType")]
        public CarType Type
        { get; set; }
    }
}

// The class with the initial values
Cars.Car car = new Cars.Car { Model = "i20", NoOfDoors = 4, Color="Red", Type=Cars.CarType.SmallCar };


//Serialized XML for the above declared class.
<Car xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" CarType="CompactCar">
  <ModelName>i20</ModelName>
  <Color>Red</Color>
  <NoOfDoors>4</NoOfDoors>
  <Price>0</Price>
</Car>

From the above class definition one can see the CarType enum is added as a property of the Car class. In the serialized XML one can see the CarType enum is serialized as an XML attribute because of the XmlAttribute applied on it in the Car class. Also note that the value of the CarType XML attribute is “CompactCar” instead “SmallCar”, this is because of the XmlEnum attribute applied in enum.

Deserializing XML

As serialization is simple deserialization is also simple. The below code shows how to deserialize an object from a xml. The above XML is used for the below deserialization.

using (System.IO.FileStream xmlFS = new System.IO.FileStream("car.xml", System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
    System.Xml.Serialization.XmlSerializer xmlSerr = new System.Xml.Serialization.XmlSerializer(typeof(Cars.Car));
    Cars.Car car = (Cars.Car)xmlSerr.Deserialize(xmlFS);
}

The below screenshot shows the properties deserialized and all the properties assigned.

image

We have seen XML serialization and how xml serialization can be controlled using different attributes. Next we will see how to serialize objects to SOAP format and attributes to control the same, till then try to know more.

Sandeep

3 comments:

  1. How to get the Serialization.XmlEnum of one property?

    ReplyDelete
  2. Hi Jack,
    Can you elaborate on what do you mean by "How to get the Serialization.XmlEnum of one property?". Thanks.

    ReplyDelete

Please provide your valuable comments.