Login   Search   
XML-FX.COM
<September 2010>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789
Using XmlSerializer with external serializable types

Using XmlSerializer with external serializable types

Table of contents

Introduction

Often XML data interfacing or exchange structures are required to be adaptable to changing business and data needs. One solution to this is to provide an xs:anyType element to the data, with the idea that this data can be provided by another code class, assembly or even another application as part of the interface realization. The xs:anyType element is unconstrained and can contain any valid XML tags and content.

Simple extensible class

In Figure 1 below, a simple extensible XML data structure is defined with the extensible unconstrained element represented by the ExternData element in the diagram.

Simple extensible XML data structure

Figure 1 : A simple extensible XML data structure

If the XML schema represented in Figure 1 above (extdata.xsd in the 'schema' solution folder) is run through an X/O mapper that supports serialization using the .NET XmlSerializer class (such as Microsoft's XSD.EXE), the class member generated for the ExternData is of type System.Object as shown below:

  1. [System.Xml.Serialization.XmlElementAttribute("ExternData")]     
  2. public object ExternData     
  3. {     
  4.     get { return externData; }     
  5.     set { externData = value; }     
  6. }  

The complete C# source-code of the serialization class is shown below (Test_1 : ExtensibleDataType.cs):

External data class

Similarly, an XML schema can be defined for the data that is contained in the ExternData element tag. For this example the schema shown in Figure 2 below is used (website.xsd in the 'schema' solution folder):

An external XML data structure to be placed in the 'ExternData' element

Figure 2 : An external XML data structure to be placed in the 'ExternData' element

Combining XML data based on the schemas in Figures 1 and 2, a sample XML data file can be produced which will validate with the schema in Listing 1 (test.002.xml in the 'schema' solution folder):

The complete C# source-code of the external data serialization class is shown below (WebsiteData : WebsiteData.cs):

Test #1: Set an instance of the external data class

The Test_1 console application (Test_1.csproj) attempts to read the file test.001.xml which contains the same data as Listing 2 without the additional ExternData element. Since this element does not exist, it is not bound to the ExternData property of the ExtensibleDataType class in Listing 1, and the value of this property is null. The application then creates a WebsiteData.Website object and then sets the ExternData property to this object. The application Main() method source-code is shown below (Program.cs):

When the object is subsequently serialized, the XmlSerializer throws an exception with the following message:

"The type WebsiteData.Website was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically."

This means that the XmlSerializer could not find the WebsiteData.Website type definition and therefore was unable to serialize the ExternData object. Although this was not unexpected, it is problematic since the original concept of allowing externally defined data to be injected into the base data is not working.

Two interesting points should be noted:

  1. If the Website class is moved into the same assembly as the ExtensibleDataType, the de-/serialization proceeds normally. However, this is still contrary to the whole extensibility concept.
  2. If the XML file test.002.xml (which contains a valid Website element) is read instead of test.001.xml, the XmlSerializer de-serializes it without error, but the ExternData now consists of two System.Xml.XmlNode objects which carry the data. Again, this is a problem, since a part of the data is now held in serialization classes (read: domain rich), while another part is held as an XML DOM, which is a model of the XML data structure and format, and not a model of the underlying domain data that serialization classes provide.

Test #2: Use a proxy serializer class

The following principle provides the solution to the problem of Test #1: a proxy object is required which can be triggered by the XmlSerializer at the correct time to de-serialize data stored in the ExternData element as an XML attribute. This must provide the type information to the XmlSerializer so that it can instantiate and bind the required external data object when de-serializing the XML data. Obviously the reverse process for serialization should also apply.

Listing 5 below provides the source-code for ExternDataSerializer which is an example of a proxy serialization class:

The following are points of interest in the implementation of the ExternDataSerializer class:

  1. In order for the class to be triggered by the XmlSerializer during de-/serialization, the IXmlSerializable interface must be implemented.
  2. In the WriteXml method, an attribute with the name 'Type' is written, and its value contains the AssemblyQualifiedName, which provides sufficient information for the XmlSerializer to correctly de-/serialize the external class, even if the class is defined in a different assembly.
  3. In the ReadXml method, the 'Type' attribute is read, the System.Type of the required external data class is determined, and the XmlSerializer can instantiate and de-serializer the object correctly.
  4. The proxy class retains a reference to the external data object created by the XmlSerializer. This is required, as the proxy class has to be implicitly cast from the external type during serialization, and to the external type during de-serialization. This functionality is provided by the two implicit cast methods.

So what else has to change in order to get this solution to work? Firstly, the external data type has to derive from a base class that is not of type System.Object. The reason for this is that the implicit cast operators cannot cast between the ExternDataSerializer class and System.Object. In Listing 3 the Website class now derives from a base class AppData.Interface.ExternData:

  1. public partial class Website : AppData.Interface.ExternData  
  2. {  
  3.   ..  
  4. }  

The AppData.Interface.ExternData class is shown below:

  1. namespace AppData.Interface  
  2. {  
  3.     public abstract class ExternData { }  
  4. }  

The AppData.Interface.ExternData class is abstract and empty. In a real application this class can provide other data which may be required as part of the data contract or interface.

The ExtensibleDataType class also has to change: first the externData member has to be of type AppData.Interface.ExternData:

  1. private AppData.Interface.ExternData externData;  

The serialization property ExternData also has to change:

  1. [System.Xml.Serialization.XmlElementAttribute("ExternData", Type = typeof(ExtData.ExternDataSerializer))]  
  2. public AppData.Interface.ExternData ExternData  
  3. {  
  4.     get { return externData; }  
  5.     set { externData = value; }  
  6. }  

Test_2 (test_2.csproj) provides the working console application implemented as above.

Dependencies and Packaging

Figure 3 below shows a class dependency diagram of the important classes:

Main classes and dependencies

Figure 3 : Main classes and dependencies

In a real application, it may be prudent to package the ExternDataSerializer and the ExternData class in the same assembly. The ExtensibleDataType is often generated automatically and repeatedly from an XML schema, and should be packaged by itself (or with other AppData types). The Website class (which represents all external data types) should be in its own assembly. Note that neither the Website or the ExtensibleDataType are directly dependent on each other. The only requirement to add other external data types to the system is to make the external data type a subclass of ExternData, and to provide a reference to the assembly containing the ExternData type.

Test #3: A parameterized proxy serializer class

Test_3 (test_3.csproj) provides a similar solution to Test_2 but using a parameterized (generic) proxy serializer class. The source code is shown in Listing 6 below:

The only other change required is the serialization property ExternData of the ExtensibleDataType class:

  1. [System.Xml.Serialization.XmlElementAttribute("ExternData", Type = typeof(ExtData.ExternDataSerializer <AppData.Interface.ExternData>))]  

Code safety

In Listing 4 line 24 and 25 the application has to check if the external data object it is working with is of the type it is expecting. Although this is annoying, it is required since the definition of the schema allows for an instance of any type to be present.

 

No Comments

Title
Author
Comment
Anti Bot Image   
  
Rss