Thursday, January 24, 2008

Binary and Soap serialization

We can serialize objects as a binary representation of them, by using the BinaryFormatter class, like this:

// Some objects to serialize
string str = "test line";
DateTime dt = DateTime.Now;
 
// Our target file stream
FileStream fs = File.Create("c:\\filename.dat");
 
// The binary formatter
BinaryFormatter bf = new BinaryFormatter();
 
// Serializing multiple objects
bf.Serialize(fs, str);
bf.Serialize(fs, dt);
 
fs.Close(); // Don't forget to close the file stream
Deserializing:

// Our source file stream
FileStream fs = File.OpenRead("c:\\filename.dat");
 
// The binary formatter
BinaryFormatter bf = new BinaryFormatter();
 
// Deserializing objects
string str = (string)bf.Deserialize(fs);
DateTime dt = (DateTime)bf.Deserialize(fs);
 
fs.Close();  // Don't forget to close the file stream
 
// Showing results
MessageBox.Show(str);
MessageBox.Show(dt.ToString());
We can set preferences for serializing objects by using Serialize Attributes. Also, if we need to perform some task after deserializing was performed, we need to implement the IDeserializationCallback interface.

Here an example using a person class that will not serialize the Age property, cause it is a calculated property.

[Serializable]
public class Person : IDeserializationCallback
{
    public string FirstName;
    public string LastName;
 
    public DateTime BirthDay;
 
    [NonSerialized]
    public int Age;
 
    [OptionalField]
    public string Alias;
 
    public Person()
    {
    }
 
    public Person(string _firstName, string _lastName, DateTime _birthDay)
    {
        FirstName = _firstName;
        LastName = _lastName;
        BirthDay = _birthDay;
 
        Age = DateTime.Now.Year - BirthDay.Year - 1;
 
        Alias = "";
    }
 
    #region IDeserializationCallback Members
 
    public void OnDeserialization(object sender)
    {
        Alias = "";
        Age = DateTime.Now.Year - BirthDay.Year - 1;
    }
 
    #endregion
}
NOTE: The OptionalField attribute has the property VersionAdded. This is reserved but we can set this with the correct version for our object, starting from 2. Example: [OptionalField(VersionAdded=2)], [OptionalField(VersionAdded=3)], and so on.

Serializing:

// Building the object instance
Person p = new Person("Oscar", "L", new DateTime(1972, 6, 16));
 
// Our target file stream
FileStream fs = File.Create("c:\\filename.dat");
 
// The binary formatter
BinaryFormatter bf = new BinaryFormatter();
 
// Serializing the person object
bf.Serialize(fs, p);
 
fs.Close(); // Don't forget to close the file stream
Deserializing:

// Our source file stream
FileStream fs = File.OpenRead("c:\\filename.dat");
 
// The binary formatter
BinaryFormatter bf = new BinaryFormatter();
 
// Deserializing the object
Person p = (Person)bf.Deserialize(fs);
 
fs.Close();  // Don't forget to close the file stream
 
// Showing results
MessageBox.Show(p.FirstName + " " + p.Age.ToString());
The code above will invoke the Person.OnDeserialization callback, so we can perform some tasks after deserialization was performed, such as, recalculate fields.

The SoapFormatter class

The SoapFormatter class works in the same way as the BinaryFormatter, except that it produces an XML Soap output, so it could be used to transport objects across the network boundaries (over Internet).

// Building the object instance
Person p = new Person("Oscar", "L", new DateTime(1972, 6, 16));
 
// Our target file stream
FileStream fs = File.Create("c:\\filename.soap");
 
// The soap formatter
SoapFormatter sf = new SoapFormatter();
 
// Serializing the person object
sf.Serialize(fs, p);
 
fs.Close(); // Don't forget to close the file stream
Here the output it produces:

<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:Person id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/nsassem/WindowsFormsApplication6/WindowsFormsApplication6%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<FirstName id="ref-3">Oscar</FirstName>
<LastName id="ref-4">L</LastName>
<BirthDay>1972-06-16T00:00:00.0000000-05:00</BirthDay>
<Alias id="ref-5"></Alias>
</a1:Person>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
And here, how we can read back the object:

// Our source file stream
FileStream fs = File.OpenRead("c:\\filename.soap");
 
// The soap formatter
SoapFormatter sf = new SoapFormatter();
 
// Deserializing the object
Person p = (Person)sf.Deserialize(fs);
 
fs.Close();  // Don't forget to close the file stream
 
// Showing results
MessageBox.Show(p.FirstName + " " + p.Age.ToString());
NOTE: For using the SoapFormatter, you need to add the reference to the System.Runtime.Serialization.Formatters.Soap library.

Implementing a custom serialization:

If we want to implement our custom serialization, we need to implement the ISerializable interface and write data when the GetObjectData is called and implement a new constructor conforms with the ISerializable specification, like this:

// New class declaration
 
[Serializable]
public class Person : IDeserializationCallback, ISerializable
{
 
...
 
// New constructor
protected Person(SerializationInfo info, StreamingContext context)
{
    this.FirstName = info.GetString("FirstName");
    this.LastName = info.GetString("LastName");
    this.BirthDay = info.GetDateTime("BirthDay");
 
    // Custom and calculated methods can be processed here    
    string another = info.GetString("another");
}
 
// Implementing the GetObjectData method
 
#region ISerializable Members
 
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
    info.AddValue("FirstName", this.FirstName);
    info.AddValue("LastName", this.LastName);
    info.AddValue("BirthDay", this.BirthDay);
 
    // Custom and other values can be writed here
    info.AddValue("another", "another value");
}
 
#endregion
 
...
 
} // End of class
Another way for controlling our serialization, can be achieve by using custom methods with serialization attributes (they transform the method in a callback that serialization will call):

[Serializable]
public class Person : IDeserializationCallback, ISerializable
{
 
...
 
[OnSerializing()]
internal void OnSerializingMethod(StreamingContext context)
{
    // During serialization
}
 
[OnSerialized()]
internal void OnSerializedMethod(StreamingContext context)
{
    // After serialization
}
 
[OnDeserializing()]
internal void OnDeserializingMethod(StreamingContext context)
{
    // During deserialization
}
 
[OnDeserialized()]
internal void OnDeserializedMethod(StreamingContext context)
{
    // After deserialization
}
 
...
 
} // End of class
NOTE: The StreamingContext object can be used to pass some important information to serialization methods (StreamingContext.Context as object) and can be used to get information about the destination (StreamingContext.State as StreamingContextStates enumeration).

// The binary formatter
StreamingContext sc = new StreamingContext(myCustomObject);
BinaryFormatter bf = new BinaryFormatter(null, sc);

0 comments:


View My Stats