n .NET 2.0, Microsoft has exposed the methods of the IXmlSerializable interface, giving you far more control over the way your objects get serialized to XML than in previous versions. Implementing IXmlSerializable in your own classes gives you explicit control over the XML schema as well as fine-grained control of the XML when serializing or deserializing your objects at runtime.
One of the most obvious weaknesses with XML serialization in .NET Framework 1.x was that it lacked the ability to customize the serialization and deserialization process. In that version, the IXmlSerializable interface provided limited serialization support geared primarily toward serializing DataSet objects. But in the .NET Framework 2.0, Microsoft has made the IXmlSerializable interface methods available for public consumption?meaning that you can implement those methods to exercise a finer level of control over the XML serialization and deserialization process. This article explores the new serialization features and demonstrates how to leverage them in your applications.
In .NET Framework 1.x you could add attributes to types at design time and also override those attributes with new values at run time; however, the entire process was based on attributes?you never really had complete control over the serialization process. The IXmlSerializable interface methods that have been present in the .NET Framework since version 1.x. (but only for internal use), are now available for general use. They are:
Of the three methods above, the GetSchema() method is now obsolete and you should not use it for serialization purposes. Accordingly, the GetSchema() method should just throw a System.NotImplementedException. Instead, you need to implement a static method that generates the schema. You then pass this static method as an argument to the XmlSchemaProviderAttribute class constructor.
The WriteXml() method converts an object into its XML representation. You have to write sufficient information to the XmlWriter stream to allow the ReadXml() method to reconstitute your object. Note that a number of classes in the .NET Framework 2.0 class library implement the IXmlSerializable interface by default, so you can serialize and deserialize them easily.
How the IXmlSerializable Methods Work
To define your very own custom serialization and deserialization process you just need to follow a few steps.
- Implement the static method that generates the schema for the type
- Decorate the type with the new attribute named XmlSchemaProvider and specify the static method that generates and inserts the schema in the XmlSchemaSet.
- Implement the ReadXml() and WriteXml() methods. The ReadXml() method controls reading the serialized format. You provide it with an XmlReader to read from the stream and populate the type. The WriteXml() method controls writing the object in serialized form. You pass it an XmlWriter to write the data to the stream.
The ReadXml() method generates an object from its XML representation. It must reconstitute your object using the information that was written before by the WriteXml() method. When you first call this method, the reader stream is positioned at exactly the point that the WriteXml() method began writing. Typically, this is immediately after the start tag that indicates the beginning of your serialized object. When this method returns, it must have consumed exactly those nodes that were written by the WriteXml method, so that reader stream is positioned where that method left it. That’s it. There is actually one more official method called GetSchema() that you must implement in the IXmlSerializable interface, but it’s there for legacy purposes only.
Implementing the IXmlSerializable Interface
Now that you’ve had the basic overview of the various IXmlSerializable interface methods, here’s an example implementation. Listing 1 shows an Employee class that implements IXmlSerializable.
The Employee class definition is marked with the XmlSchemaProvider attribute.
The XmlSchemaProviderAttribute class indicates the name of the method to call to get the schema added to the internal schema set. It takes an XmlSchemaSet and returns the XmlQualifiedName of the starting element.
Reading and Writing the XML
The ReadXml() method implementation of the Employee class in Listing 1 uses the XmlReader input argument to read the XML and populates the Employee class’s local variables with the values read.
Public Sub ReadXml(ByVal reader As XmlReader) _ Implements IXmlSerializable.ReadXml Dim type As XmlNodeType = reader.MoveToContent() If ((type = XmlNodeType.Element) _ And (reader.LocalName = "employee")) Then _firstName = reader("firstName") _lastName = reader("lastName") _address = reader("address") End If End Sub
The WriteXml() method implementation writes an XML representation of the Employee class, including all the local variables, using the various methods of the XmlWriter class.
Public Sub WriteXml(ByVal writer As XmlWriter) _ Implements IXmlSerializable.WriteXml writer.WriteStartElement _ ("employee", "urn:devx-com") writer.WriteAttributeString _ ("firstName", _firstName) writer.WriteAttributeString _ ("lastName", _lastName) writer.WriteAttributeString _ ("address", _address) writer.WriteEndElement() End Sub
The major chunk of the code in Listing 1 resides in the CreateEmployeeSchema() method, which leverages the XML Schema Object Model (SOM) to create an XML schema dynamically and add that to the XmlSchemaSet object. The code is somewhat repetitive; in this case, it creates an XmlSchemaComplexType and then creates XmlSchemaAttribute and XmlSchemaElement instances to hold the content.
Now that you have implemented the Employee class, you can leverage it from a Web service. The EmployeeService class shown below implements a GetEmployee() method that returns an Employee object.
Imports System.Web Imports System.Web.Services Imports System.Web.Services.Protocols
? Figure 1. Employee Class Definition in WSDL: Inspecting the WSDL shows you the contents of the element, along with the element _ _ Public Class EmployeeService Inherits System.Web.Services.WebService _ Public Function GetEmployee() As Employee Dim emp As New Employee("Thiru", _ "Thangarathinam", "2644 E Remington " & _ "Place, Chandler, AZ") Return emp End Function End Class
The GetEmployee() method is very simple and straightforward. It just creates an instance of the Employee object and returns that object back to the caller. If you inspect the WSDL of the Web service, you will see the definition of the Employee class as shown in Figure 1. The figure shows the XSD schema representation of the Employee class generated by CreateEmployeeSchema().
|Figure 2. Output produced by the EmployeeService: The EmployeeService creates and then serializes an Employee object using the WriteXml() method of the IXmlSerializable interface.
If you browse to the Web service URL in a browser and invoke the GetEmployee() method, you’ll see the output shown in Figure 2.
Keep It Short
The point to remember here is that using the IXmlSerializable gives you control over how the .NET framework 2.0 serializes your objects. That capability opens up new avenues of opportunities and interesting scenarios that you can use to create sophisticated Web services using .NET Framework 2.0. For example, you would want this type of control when streaming large amounts of data from a Web application. To enable data streaming, you have to turn off response buffering and then chunk the data into discrete blocks demarcated with XML elements. Using IXmlSerializable, you can control the schema for the chunking format and also control the reading and writing of data to the stream with the ReadXml() and WriteXml() methods. Such XML customization is also very useful when you are exposing your Web services to clients from different platforms and you need to be able to have a fine level of control over how the types are serialized.