
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
722 CHAPTER 21 ■ INTRODUCING OBJECT SERIALIZATION
By default, all public data of a [Serializable] type is formatted as elements rather than XML attributes. If you wish to control how the XmlSerializer generates the resulting XML document, you may decorate your [Serializable] types with any number of additional attributes from the System. Xml.Serialization namespace. Table 21-1 documents some (but not all) of the attributes that influence how XML data is encoded to a stream.
Table 21-1. Select Attributes of the System.Xml.Serialization Namespace
Attribute |
Meaning in Life |
XmlAttributeAttribute |
The member will be serialized as an XML attribute. |
XmlElementAttribute |
The field or property will be serialized as an XML element. |
XmlEnumAttribute |
The element name of an enumeration member. |
XmlRootAttribute |
This attribute controls how the root element will be constructed |
|
(namespace and element name). |
XmlTextAttribute |
The property or field should be serialized as XML text. |
XmlTypeAttribute |
The name and namespace of the XML type. |
|
|
By way of a simple example, first consider how the field data of JamesBondCar is currently persisted as XML:
<?xml version="1.0" encoding="utf-8"?>
<JamesBondCar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
...
<canFly>true</canFly>
<canSubmerge>false</canSubmerge>
</JamesBondCar>
If you wish to specify a custom XML namespace that qualifies the JamesBondCar as well as encodes the canFly and canSubmerge values as XML attributes, you can do so by modifying the C# definition of JamesBondCar as so:
[Serializable,
XmlRoot(Namespace = "http://www.intertech.com")] public class JamesBondCar : Car
{
[XmlAttribute] public bool canFly;
[XmlAttribute]
public bool canSubmerge;
}
This yields the following XML document (note the opening <JamesBondCar> element):
<?xml version="1.0"""?>
<JamesBondCar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
canFly="true" canSubmerge="false" xmlns="http://www.intertechtraining.com">
...
</JamesBondCar>
Of course, numerous other attributes can be used to control how the XmlSerializer generates the resulting XML document. For full details, look up the System.Xml.Serialization namespace using the .NET Framework 3.5 SDK documentation.

CHAPTER 21 ■ INTRODUCING OBJECT SERIALIZATION |
723 |
Serializing Collections of Objects
Now that you have seen how to persist a single object to a stream, let’s examine how to save a set of objects. As you may have noticed, the Serialize() method of the IFormatter interface does not provide a way to specify an arbitrary number of objects as input (only a single System.Object). On a related note, the return value of Deserialize() is, again, a single System.Object (the same basic limitation holds true for the XmlSerializer):
public interface IFormatter
{
...
object Deserialize(System.IO.Stream serializationStream);
void Serialize(System.IO.Stream serializationStream, object graph);
}
Recall that the System.Object in fact represents a complete tree of objects. Given this, if you pass in an object that has been marked as [Serializable] and contains other [Serializable] objects, the entire set of objects is persisted in a single method call. As luck would have it, most of the types found within the System.Collections and System.Collections.Generic namespaces have already been marked as [Serializable]. Therefore, if you wish to persist a set of objects, simply add the set to the container (such as an ArrayList or a List<T>) and serialize the object to your stream of choice.
Assume you have updated the JamesBondCar class with a two-argument constructor to set a few pieces of state data (note that you add back the default constructor as required by the
XmlSerializer):
[Serializable,
XmlRoot(Namespace = "http://www.intertech.com")] public class JamesBondCar : Car
{
public JamesBondCar(bool skyWorthy, bool seaWorthy)
{
canFly = skyWorthy; canSubmerge = seaWorthy;
}
// The XmlSerializer demands a default constructor! public JamesBondCar(){}
...
}
With this, you are now able to persist any number of JamesBondCars as so:
static void SaveListOfCars()
{
// Now persist a List<T> of JamesBondCars.
List<JamesBondCar> myCars = new List<JamesBondCar>(); myCars.Add(new JamesBondCar(true, true)); myCars.Add(new JamesBondCar(true, false)); myCars.Add(new JamesBondCar(false, true)); myCars.Add(new JamesBondCar(false, false));
using(Stream fStream = new FileStream("CarCollection.xml", FileMode.Create, FileAccess.Write, FileShare.None))
{
XmlSerializer xmlFormat = new XmlSerializer(typeof(List<JamesBondCar>), new Type[] { typeof(JamesBondCar), typeof(Car), typeof(Radio) });
xmlFormat.Serialize(fStream, myCars);

724 CHAPTER 21 ■ INTRODUCING OBJECT SERIALIZATION
}
Console.WriteLine("=> Saved list of cars!");
}
Here, because you made use of the XmlSerializer, you are required to specify type information for each of the subobjects within the root object (which in this case is the List<JamesBondCar>). Had you made use of the BinaryFormatter or SoapFormatter type, the logic would be even more straightforward, for example:
static void SaveListOfCarsAsBinary()
{
// Save ArrayList object (myCars) as binary.
List<JamesBondCar> myCars = new List<JamesBondCar>();
BinaryFormatter binFormat = new BinaryFormatter(); using(Stream fStream = new FileStream("AllMyCars.dat",
FileMode.Create, FileAccess.Write, FileShare.None))
{
binFormat.Serialize(fStream, myCars);
}
Console.WriteLine("=> Saved list of cars in binary!");
}
■Source Code The SimpleSerialize application is located under the Chapter 21 subdirectory.
Customizing the Serialization Process
In a majority of cases, the default serialization scheme provided by the .NET platform will be exactly what you require. Simply apply the [Serializable] attribute to your related types and pass the tree of objects to your formatter of choice for processing. In some cases, however, you may wish to become more involved with how a tree is constructed and handled during the serialization process. For example, maybe you have a business rule that says all field data must be persisted using a particular format, or perhaps you wish to add additional bits of data to the stream that do not directly map to fields in the object being persisted (timestamps, unique identifiers, or whatnot).
When you wish to become more involved with the process of object serialization, the System.Runtime.Serialization namespace provides several types that allow you to do so. Table 21-2 describes some of the core types to be aware of.
Table 21-2. System.Runtime.Serialization Namespace Core Types
Type |
Meaning in Life |
ISerializable |
This interface can be implemented on a [Serializable] type to control its |
|
serialization and deserialization. |
ObjectIDGenerator |
This type generates IDs for members in an object graph. |
[OnDeserialized] |
This attribute allows you to specify a method that will be called |
|
immediately after the object has been deserialized. |
[OnDeserializing] |
This attribute allows you to specify a method that will be called before the |
|
deserialization process. |

CHAPTER 21 ■ INTRODUCING OBJECT SERIALIZATION |
725 |
Type |
Meaning in Life |
[OnSerialized] |
This attribute allows you to specify a method that will be called |
|
immediately after the object has been serialized. |
[OnSerializing] |
This attribute allows you to specify a method that will be called before the |
|
serialization process. |
[OptionalField] |
This attribute allows you to define a field on a type that can be missing |
|
from the specified stream. |
SerializationInfo |
In essence, this class is a “property bag” that maintains name/value pairs |
|
representing the state of an object during the serialization process. |
|
|
A Deeper Look at Object Serialization
Before we examine various ways in which you can customize the serialization process, it will be helpful to take a deeper look at what takes place behind the scenes. When the BinaryFormatter serializes an object graph, it is in charge of transmitting the following information into the specified stream:
•The fully qualified name of the objects in the graph (e.g., MyApp.JamesBondCar)
•The name of the assembly defining the object graph (e.g., MyApp.exe)
•An instance of the SerializationInfo class that contains all stateful data maintained by the members in the object graph
During the deserialization process, the BinaryFormatter uses this same information to build an identical copy of the object, using the information extracted from the underlying stream. The process used by the SoapFormatter is quite similar.
■Note Recall that the XmlSerializer does not persist a type’s fully qualified name or the name of the defining assembly in order to keep the state of the object as mobile as possible. This type is concerned only with persisting exposed public data.
Beyond moving the required data into and out of a stream, formatters also analyze the members in the object graph for the following pieces of infrastructure:
•A check is made to determine whether the object is marked with the [Serializable] attribute. If the object is not, a SerializationException is thrown.
•If the object is marked [Serializable], a check is made to determine if the object implements the ISerializable interface. If this is the case, GetObjectData() is called on the object.
•If the object does not implement ISerializable, the default serialization process is used, serializing all fields not marked as [NonSerialized].
In addition to determining if the type supports ISerializable, formatters are also responsible for discovering if the types in question support members that have been adorned with the
[OnSerializing], [OnSerialized], [OnDeserializing], or [OnDeserialized] attribute. We’ll examine the role of these attributes in just a bit, but first let’s look at the role of ISerializable.

726 CHAPTER 21 ■ INTRODUCING OBJECT SERIALIZATION
Customizing Serialization Using ISerializable
Objects that are marked [Serializable] have the option of implementing the ISerializable interface. By doing so, you are able to “get involved” with the serialization process and perform any preor post-data formatting.
■Note Since the release of .NET 2.0, the preferred way to customize the serialization process is to use the serialization attributes (described next). However, knowledge of ISerializable is important for the purpose of maintaining existing systems.
The ISerializable interface is quite simple, given that it defines only a single method,
GetObjectData():
//When you wish to tweak the serialization process,
//implement ISerializable.
public interface ISerializable
{
void GetObjectData(SerializationInfo info, StreamingContext context);
}
The GetObjectData() method is called automatically by a given formatter during the serialization process. The implementation of this method populates the incoming SerializationInfo parameter with a series of name/value pairs that (typically) map to the field data of the object being persisted. SerializationInfo defines numerous variations on the overloaded AddValue() method, in addition to a small set of properties that allow the type to get and set the type’s name, defining assembly, and member count. Here is a partial snapshot:
public sealed class SerializationInfo : object
{
public SerializationInfo(Type type, IFormatterConverter converter); public string AssemblyName { get; set; }
public string FullTypeName { get; set; } public int MemberCount { get; }
public void AddValue(string name, short value); public void AddValue(string name, UInt16 value); public void AddValue(string name, int value);
...
}
Types that implement the ISerializable interface must also define a special constructor taking the following signature:
//You must supply a custom constructor with this signature
//to allow the runtime engine to set the state of your object.
[Serializable]
class SomeClass : ISerializable
{
protected SomeClass (SerializationInfo si, StreamingContext ctx) {...}
...
}
Notice that the visibility of this constructor is set as protected. This is permissible given that the formatter will have access to this member regardless of its visibility. These special constructors tend to be marked as protected (or private for that matter) to ensure that the casual object user would

CHAPTER 21 ■ INTRODUCING OBJECT SERIALIZATION |
727 |
never create an object in this manner. As you can see, the first parameter of this constructor is an instance of the SerializationInfo type (seen previously).
The second parameter of this special constructor is a StreamingContext type, which contains information regarding the source or destination of the bits. The most informative member of this type is the State property, which represents a value from the StreamingContextStates enumeration. The values of this enumeration represent the basic composition of the current stream.
To be honest, unless you are implementing some low-level custom remoting services, you will seldom need to deal with this enumeration directly. Nevertheless, here are the possible names of the StreamingContextStates enum (consult the .NET Framework 3.5 SDK documentation for full details):
public enum StreamingContextStates
{
CrossProcess,
CrossMachine,
File,
Persistence,
Remoting,
Other,
Clone,
CrossAppDomain, All
}
To illustrate customizing the serialization process using ISerializable, assume you have a new Console Application project (named CustomSerialization) that defines a class type containing two points of string data. Furthermore, assume that you must ensure the string objects are serialized to the stream in all uppercase and deserialized from the stream in all lowercase. To account for such rules, you could implement ISerializable as so (be sure to import the System.Runtime. Serialization namespace):
[Serializable]
class StringData : ISerializable
{
public string dataItemOne = "First data block"; public string dataItemTwo= "More data";
public StringData(){}
protected StringData(SerializationInfo si, StreamingContext ctx)
{
// Rehydrate member variables from stream. dataItemOne = si.GetString("First_Item").ToLower(); dataItemTwo = si.GetString("dataItemTwo").ToLower();
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext ctx)
{
// Fill up the SerializationInfo object with the formatted data. info.AddValue("First_Item", dataItemOne.ToUpper()); info.AddValue("dataItemTwo", dataItemTwo.ToUpper());
}
}
Notice that when you are filling the SerializationInfo type from within the GetObjectData() method, you are not required to name the data points identically to the type’s internal member variables. This can obviously be helpful if you need to further decouple the type’s data from the persisted format. Do be aware, however, that you will need to obtain the values from within the special protected constructor using the same names assigned within GetObjectData().

728 CHAPTER 21 ■ INTRODUCING OBJECT SERIALIZATION
To test your customization, assume you have persisted an instance of MyStringData using a
SoapFormatter:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Custom Serialization *****");
//Recall that this type implements ISerializable.
StringData myData = new StringData();
//Save to a local file in SOAP format.
SoapFormatter soapFormat = new SoapFormatter(); using(Stream fStream = new FileStream("MyData.soap",
FileMode.Create, FileAccess.Write, FileShare.None))
{
soapFormat.Serialize(fStream, myData);
}
Console.ReadLine();
}
When you view the resulting *.soap file, you will note that the string fields have indeed been persisted in uppercase (see Figure 21-5).
Figure 21-5. Customizing our serialization via ISerializable
Customizing Serialization Using Attributes
Although implementing the ISerializable interface is one possible way to customize the serialization process, since the release of .NET 2.0 the preferred way to customize the serialization process is to define methods that are attributed with any of the new serialization-centric attributes:
[OnSerializing], [OnSerialized], [OnDeserializing], or [OnDeserialized]. Using these attributes is less cumbersome than implementing ISerializable, given that you do not need to manually interact with an incoming SerializationInfo parameter. Instead, you are able to directly modify your state data while the formatter is operating on the type.
■Note These serialization attributes are defined within the System.Runtime.Serialization namespace.
When applying these attributes, the methods must be defined to receive a StreamingContext parameter and return nothing (otherwise, you will receive a runtime exception). Do note that you are not required to account for each of the serialization-centric attributes, and you can simply contend with the stages of serialization you are interested in intercepting. To illustrate, here is a new

CHAPTER 21 ■ INTRODUCING OBJECT SERIALIZATION |
729 |
[Serializable] type that has the same requirements as StringData, this time accounted for using the [OnSerializing] and [OnDeserialized] attributes:
[Serializable] class MoreData
{
public string dataItemOne = "First data block"; public string dataItemTwo= "More data";
[OnSerializing]
private void OnSerializing(StreamingContext context)
{
// Called during the serialization process. dataItemOne = dataItemOne.ToUpper(); dataItemTwo = dataItemTwo.ToUpper();
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
// Called once the deserialization process is complete. dataItemOne = dataItemOne.ToLower();
dataItemTwo = dataItemTwo.ToLower();
}
}
If you were to serialize this new type, you would again find that the data has been persisted as uppercase and deserialized as lowercase.
■Source Code The CustomSerialization project is included under the Chapter 21 subdirectory.
With this example behind us, you have now been exposed to the core details regarding object serialization services, including various ways to customize the process. As you have seen, the serialization and deserialization process makes it very simple to persist large amounts of data, and it can be less labor-intensive than working with the various reader/writer classes of the System.IO namespace. If you wish to dig deeper into this aspect of the .NET platform, be sure to look up the
BinaryFormatter, SoapFormatter, and XmlSerializer using the .NET Framework 3.5 SDK documentation.
Summary
This chapter introduced the topic of object serialization services. As you have seen, the .NET platform makes use of an object graph to correctly account for the full set of related objects that are to be persisted to a stream. As long as each member in the object graph has been marked with the [Serializable] attribute, the data is persisted using your format of choice (binary, SOAP, or XML).
You also learned that it is possible to customize the out-of-the-box serialization process using two possible approaches. First, you learned how to implement the ISerializable interface (and support a special private constructor) to become more involved with how formatters persist the supplied data. Next, you came to know a set of .NET attributes that simplify the process of custom serialization. Just apply the [OnSerializing], [OnSerialized], [OnDeserializing], or
[OnDeserialized] attribute on members taking a StreamingContext parameter, and the formatters will invoke them accordingly.


C H A P T E R 2 2
ADO.NET Part I:
The Connected Layer
As you would expect, the .NET platform defines a number of namespaces that allow you to interact with machine local and remote relational databases. Collectively speaking, these namespaces are known as ADO.NET. In this chapter, once I frame the overall role of ADO.NET, I’ll move on to discuss the topic of ADO.NET data providers. The .NET platform supports numerous data providers, each of which is optimized to communicate with a specific database management system (Microsoft SQL Server, Oracle, MySQL, etc.).
After you understand the common functionality provided by various data providers, you will then examine the data provider factory pattern. As you will see, using types within the System.Data. Common namespace (and a related App.config file), you are able to build a single code base that can dynamically pick and choose the underlying data provider without the need to recompile or redeploy the application’s code base.
Perhaps most importantly, this chapter will give you the chance to build a custom data access library assembly (AutoLotDAL.dll) that will encapsulate various database operations performed on a custom database named AutoLot. This library will be expanded upon in Chapter 23 and leveraged over many of this text’s remaining chapters. We wrap things up by examining how to communicate with Microsoft SQL Server in an asynchronous manner using the types within the System.Data.
SqlClient namespace and introduce the topic of database transactions.
A High-Level Definition of ADO.NET
If you have a background in Microsoft’s previous COM-based data access model (Active Data Objects, or ADO), understand that ADO.NET has very little to do with ADO beyond the letters “A,” “D,” and “O.” While it is true that there is some relationship between the two systems (e.g., each has the concept of connection and command objects), some familiar ADO types (e.g., the Recordset) no longer exist. Furthermore, there are a number of new ADO.NET types that have no direct equivalent under classic ADO (e.g., the data adapter).
Unlike classic ADO, which was primarily designed for tightly coupled client/server systems, ADO.NET was built with the disconnected world in mind, using DataSets. This type represents a local copy of any number of related data tables, each of which contain a collection of rows and column. Using the DataSet, the calling assembly (such as a web page or desktop executable) is able to manipulate and update a DataSet’s contents while disconnected from the data source, and send any modified data back for processing using a related data adapter.
Another major difference between classic ADO and ADO.NET is that ADO.NET has deep support for XML data representation. In fact, the data obtained from a data store is serialized (by default) as XML. Given that XML is often transported between layers using standard HTTP,
ADO.NET is not limited by firewall constraints.
731