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.
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.
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).
<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.
18.118.31.67