DekGenius.com
[ Team LiB ] Previous Section Next Section

9.2 Runtime Serialization

Runtime serialization is used to serialize objects to binary or user-defined formats. In mapping CLR types to serialization format, the CLR type is favored; that is, it is assumed that both ends of the serialization channel understand how to map any given CLR type to a serialization format. With runtime serialization, you're guaranteed full fidelity between the objects you started with and the new objects you end up with. You can use one of the concrete formatter classes (BinaryFormatter or SoapFormatter) to serialize your data, or you can write your own class that implements IFormatter or extends Formatter to do the work.

In runtime serialization, serializable objects may be marked as such with the Serializable attribute, in which case the IFormatter class does all the work of serialization. Alternatively, a serializable object may implement ISerializable, in which case you are responsible for implementing the GetObjectData( ) method to provide the necessary information to the IFormatter.

Because the built-in formatters favor CLR datatypes, .NET remoting uses them to serialize objects. This also means that the SoapFormatter assumes that the remote end of the serialization stream knows about the CLR, and how to convert objects from their SOAP representation to CLR types. This is fine for homogeneous systems, but the point of XML is to enable disparate systems to communicate. SOAP is useful for such communication between disparate systems, because it provides an XML schema that can contain all the information necessary to recreate an object remotely.

Example 9-4 shows the code that defines Angus Hardware's personnel records. I'll use this code throughout the examples in this chapter.

Example 9-4. Angus Hardware personnel records
public enum AddressType {
  Home,
  Office,
  Billing,
  Shipping,
  Mailing,
  Day,
  Evening,
  FAX
}

public enum State {
  AK, AL, AR, AZ, CA, CO,
  CT, DC, DE, FL, GA, HI,
  IA, ID, IL, IN, KS, KY,
  LA, MA, MD, ME, MI, MN,
  MO, MS, MT, NC, ND, NE,
  NH, NJ, NM, NV, NY, OH,
  OK, OR, PA, PR, RI, SC,
  SD, TN, TX, UT, VA, WA,
  WI, WV, WY
}

public class Address {
  public AddressType AddressType;
  public string[ ] Street;
  public string City;
  public State State;
  public string Zip;
}

public class TelephoneNumber {
  public string AreaCode;
  public string Exchange;
  public string Number;
}

public class Employee {
  public string FirstName;
  public string MiddleInitial;
  public string LastName;
  public Address [ ] Addresses;
  public TelephoneNumber [ ] TelephoneNumbers;
  public DateTime HireDate;
}

public class Personnel {
  public Employee [ ] Employees;
}

To use these objects, of course, you would use a method something like this:

private static Personnel CreatePersonnel( ) {
  Personnel personnel = new Personnel( );
  personnel.Employees = new Employee [ ] {new Employee( )};
  personnel.Employees[0].FirstName = "Niel";
  personnel.Employees[0].MiddleInitial = "M";
  personnel.Employees[0].LastName = "Bornstein";
    
  personnel.Employees[0].Addresses = new Address [ ] {new Address( )};
  personnel.Employees[0].Addresses[0].AddressType = AddressType.Home;
  personnel.Employees[0].Addresses[0].Street = new string [ ] {"999 Wilford Trace"};
  personnel.Employees[0].Addresses[0].City = "Atlanta";
  personnel.Employees[0].Addresses[0].State = State.GA;
  personnel.Employees[0].Addresses[0].Zip = "30037";
  personnel.Employees[0].HireDate = new DateTime(2001,1,1);
}

To serialize these objects to SOAP, you need to use the SoapFormatter. Here's the Main( ) method of a program that uses the personnel objects and the CreatePersonnel( ) method defined above to serialize a personnel record to SOAP:

public static void Main(string [ ] args) {
  Personnel personnel = CreatePersonnel( );
  IFormatter soapFormatter = new SoapFormatter( );
  using (FileStream stream = File.OpenWrite("PersonnelSoap.xml")) {
    soapFormatter.Serialize(stream,personnel);
  }
}

If you run it as is, you'll get the following exception:

Unhandled Exception: System.Runtime.Serialization.SerializationException: The type 
Personnel in Assembly personnelSoap, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null is not marked as serializable.
...

What went wrong? The SoapFormatter does not know how to serialize the type Personnel because it is not marked as serializable with the Serializable attribute. If you go back and apply that attribute to Personnel, you'll get the same exception for each of the Personnel object's fields.

In order to serialize an object using runtime serialization, it, and each object it contains, must either be marked as serializable or implement the ISerializible interface. Example 9-5 shows the complete program with Serializable attributes.

Example 9-5. Program to serialize personnel records to SOAP
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;

[Serializable]
public enum AddressType {
  Home,
  Office,
  Billing,
  Shipping,
  Mailing,
  Day,
  Evening,
  FAX
}

[Serializable]
public enum State {
  AK, AL, AR, AZ, CA, CO,
  CT, DC, DE, FL, GA, HI,
  IA, ID, IL, IN, KS, KY,
  LA, MA, MD, ME, MI, MN,
  MO, MS, MT, NC, ND, NE,
  NH, NJ, NM, NV, NY, OH,
  OK, OR, PA, PR, RI, SC,
  SD, TN, TX, UT, VA, WA,
  WI, WV, WY
}

[Serializable]
public class Address {
  public AddressType AddressType;
  public string[ ] Street;
  public string City;
  public State State;
  public string Zip;
}

[Serializable]
public class TelephoneNumber {
  public AddressType AddressType;
  public string AreaCode;
  public string Exchange;
  public string Number;
}

[Serializable]
public class Employee {
  public string FirstName;
  public string MiddleInitial;
  public string LastName;
  
  public Address [ ] Addresses;
public TelephoneNumber [ ] TelephoneNumbers;

  public DateTime HireDate;
}

[Serializable]
public class Personnel {
  public Employee [ ] Employees;
}

public class Serializer {
  public static void Main(string [ ] args) {
    IFormatter formatter = new SoapFormatter( );
    Personnel personnel = CreatePersonnel( );
    formatter.Serialize(File.OpenWrite("PersonnelSoap.xml"),personnel);
  }
  
  private static Personnel CreatePersonnel( ) {
    Personnel personnel = new Personnel( );
    personnel.Employees = new Employee [ ] {new Employee( )};
    personnel.Employees[0].FirstName = "Niel";
    personnel.Employees[0].MiddleInitial = "M";
    personnel.Employees[0].LastName = "Bornstein";
    
    personnel.Employees[0].Addresses = new Address [ ] {new Address( )};
    personnel.Employees[0].Addresses[0].AddressType = AddressType.Home;
    personnel.Employees[0].Addresses[0].Street = 
      new string [ ] {"999 Wilford Trace"};
    personnel.Employees[0].Addresses[0].City = "Atlanta";
    personnel.Employees[0].Addresses[0].State = State.GA;
    personnel.Employees[0].Addresses[0].Zip = "30037";
    personnel.Employees[0].HireDate = new DateTime(2001,1,1);
    
    return personnel;
  }
}

The SOAP instance that this code produces looks pretty much like the ones you saw before, except that there's a lot more information included. These SOAP messages include everything the .NET Framework needs to completely reconstruct all the objects in the Personnel instance I serialized. I'll walk you through the generated SOAP data to explain what's been done:

<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/">

The SOAP-ENV:Envelope element seems to have all the standard namespaces you'd expect for any SOAP message. One, however, is a little different. The clr prefix, assigned to the URI http://schemas.microsoft.com/soap/encoding/clr/1.0, represents the encoding for types in the .NET CLR. There is no actual web page at that URI; it's just used as a convention to indicate CLR encoding, which each instance of the .NET Framework inherently knows how to do:

<SOAP-ENV:Body>
  <a1:Personnel id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/assem/
personnelSoap%2C%20Version%3D0.0.0.
0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">

The a1:Personnel element represents an instance of the Personnel type. The a1 namespace prefix, assigned to the URI http://schemas.microsoft.com/clr/assem/personnelSoap%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull, is used to mark types defined in the personnelSoap assembly; that is, the assembly generated from the source in Example 9-5.

Likewise, there is no web page at the URI http://schemas.microsoft.com/ clr/assem/personnelSoap%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken% 3Dnull. How would Microsoft know anything about the personnelSoap assembly you just generated? Like the clr prefix, the namespace URI is used as a convention to indicate what assembly the types come from, so that the object can be recreated correctly when reading it in from the serialization stream.

If you decode the URI in the previous paragraph, you'll see that it actually consists of two parts: http://schemas.microsoft.com/clr/assem/, which indicates that the elements represent types defined in a CLR assembly; and personnelSoap, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null, which indicates the particular assembly that defines the types. The assembly is identified by the return value of its ToString( ) method.


This instance of a1:Personnel is given the id attribute with the value ref-1, in case it's needed for future reference:

  <Employees href="#ref-3"/>
</a1:Personnel>

The Employees element here indicates that the instance of a1:Personnel contains the instance of SOAP-ENC:Array with the id value of ref-3. That instance will be defined later in the SOAP document:

<SOAP-ENC:Array id="ref-3" SOAP-ENC:arrayType="a1:Employee[1]" 
xmlns:a1="http://schemas.microsoft.com/clr/assem/personnelSoap%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
  <item href="#ref-4"/>
</SOAP-ENC:Array>

The SOAP-ENC:Array element defines the encoding for an array. In this case, it's an array of one Employee (SOAP-ENC:arrayType="a1:Employee[1]"). The array's id value is ref-3, which was referenced above by the a1:Personnel element. The actual array elements are contained in item elements; the one element of this array refers to the object with id value ref-4:

<a1:Employee id="ref-4" xmlns:a1="http://schemas.microsoft.com/clr/assem/personnelSoap%
2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
  <FirstName id="ref-5">Niel</FirstName>
  <MiddleInitial id="ref-6">M</MiddleInitial>
  <LastName id="ref-7">Bornstein</LastName>
  <Addresses href="#ref-8"/>
  <TelephoneNumbers xsi:null="1"/>
  <HireDate>2001-01-01T00:00:00.0000000-05:00</HireDate>
</a1:Employee>

This a1:Employee element represents an actual instance of the Employee object with the id value of ref-4, which represents an item in the array. The first three of its child elements are simple string types, and their values are included inline. The Addresses and TelephoneNumbers elements, however, are arrays, and as such are referenced by their id attribute values:

<SOAP-ENC:Array id="ref-8" SOAP-ENC:arrayType="a1:Address[1] " 
xmlns:a1="http://schemas.microsoft.com/clr/assem/personnelSoap%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
  <item href="#ref-9"/>
</SOAP-ENC:Array>

This SOAP-ENC:Array element represents an array of a1:Address elements:

<a1:Address id="ref-9" xmlns:a1="http://schemas.microsoft.com/clr/assem/personnelSoap%
2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
  <AddressType>Home</AddressType>
  <Street href="#ref-10"/>
  <City id="ref-11">Atlanta</City>
  <State>GA</State>
  <Zip id="ref-12">30037</Zip>
</a1:Address>

This a1:Address element represents an array element, and itself contains another array of a1:Street elements:

    <SOAP-ENC:Array id="ref-10" SOAP-ENC:arrayType="xsd:string[1]">
      <item id="ref-13">999 Wilford Trace</item>
    </SOAP-ENC:Array>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Finally, there's the a1:Street element, and that's the end. Example 9-6 shows the complete serialized PersonnelSoap.xml file (with indentation added to make it easier to read).

Example 9-6. Personnel records serialized as SOAP
<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:Personnel id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/assem/
personnelSoap%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
      <Employees href="#ref-3"/>
    </a1:Personnel>
    <SOAP-ENC:Array id="ref-3" SOAP-ENC:arrayType="a1:Employee[1]"
xmlns:a1="http://schemas.microsoft.com/clr/assem/personnelSoap%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
      <item href="#ref-4"/>
    </SOAP-ENC:Array>
    <a1:Employee id="ref-4" xmlns:a1="http://schemas.microsoft.com/clr/assem/
personnelSoap%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
      <FirstName id="ref-5">Niel</FirstName>
      <MiddleInitial id="ref-6">M</MiddleInitial>
      <LastName id="ref-7">Bornstein</LastName>
      <Addresses href="#ref-8"/>
      <TelephoneNumbers xsi:null="1"/>
      <HireDate>2001-01-01T00:00:00.0000000-05:00</HireDate>
    </a1:Employee>
    <SOAP-ENC:Array id="ref-8" SOAP-ENC:arrayType="a1:Address[1]" xmlns:a1=
"http://schemas.microsoft.com/clr/assem/personnelSoap%2C%20Version%3D0.0.0.0%2C%20Culture%
3Dneutral%2C%20PublicKeyToken%3Dnull">
      <item href="#ref-9"/>
    </SOAP-ENC:Array>
    <a1:Address id="ref-9" xmlns:a1="http://schemas.microsoft.com/clr/assem/
personnelSoap%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
      <AddressType>Home</AddressType>
      <Street href="#ref-10"/>
      <City id="ref-11">Atlanta</City>
      <State>GA</State>
      <Zip id="ref-12">30037</Zip>
    </a1:Address>
    <SOAP-ENC:Array id="ref-10" SOAP-ENC:arrayType="xsd:string[1]">
      <item id="ref-13">999 Wilford Trace</item>
    </SOAP-ENC:Array>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

This serialized object can be read in by any assembly that has access to the PersonnelSoap assembly because of the namespace URI referencing personnelSoap, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null. The code to deserialize a SOAP-serialized object is simple:

IFormatter formatter = new SoapFormatter( );
Personnel personnel = (Personnel)formatter.Deserialize(
  File.OpenRead("PersonnelSoap.xml"));

I haven't really touched on it, but the same process works equally well for binary serialization. Just use an instance of System.Runtime.Serialization.Formatter.Binary.BinaryFormatter instead of System.Runtime.Serialization.Formatter.Soap.SoapFormatter. The difference is that you can't write binary serialized objects to a text stream, as they contain binary data that won't be preserved in an 8-bit text stream environment.


Another way to control runtime serialization is to implement the ISerializable interface. When you use Serializable, all the work is on the SoapFormatter or BinaryFormatter, but the ISerializable interface requires you to implement the method GetObjectData( ).

GetObjectData( ) takes two parameters. The first is an instance of SerializationInfo to be populated by the method, and the second is an instance of StreamContext giving information about the actual bits being serialized or deserialized. This information is obtained at runtime, which gives runtime serialization its name.

Clearly, SOAP and binary serialization require that both the reader and the writer of the serialized data have full knowledge of the CLR and its types. Since one of the major goals of XML is to make disparate systems work together, runtime serialization places some extra requirements on the program doing the deserializing.

    [ Team LiB ] Previous Section Next Section