Mark Wilson I am the creator of TopXML. I am available for international and local (Australia) contracts. I am a Solution Architect/Business Analyst. I have worked in IT in several countries (NZ, Australia, South Africa, UK) building and training teams for government and very large non-governmental organizations. I am ex-Microsoft Consulting Services. I wrote the first book on Microsoft XML published in 2000 called XML Programming with VB and ASP. Most recently I have been building tools for the SEO industry. Ask me for a 37 point SEO health-checkup for your website.
First posted :
03/24/2008
Times viewed :
358
Deserializing an Object Graph
Now that we can send an object as a SOAP message and we know
what the message contains, we can go on to develop the next class for our
SOAP-over-message-queues library. This class will process the SOAP messages
coming in on a message queue, deserialize the objects contained in the message
and execute a method on the deserialized object.
The best class to interpret the contents of the SOAP message is
the same class that already created the message for us: The SoapFormatter.
It exposes two overloaded versions of the Deserialize() method.
Table The two overloaded versions of the Deserialize()
method read a SOAP message from a stream and return the object graph stored in
the body. The second version allows us the register a method to receive
notifications about application headers in the SOAP message.
Method
Description
public object Deserialize( Stream s )
Deserializes an object graph in the body of a SOAP message
from the current position of the stream.
public object Deserialize( Stream s, HeaderHandler h )
Deserializes an object graph in the body of a SOAP message
from the current position of the stream. A HeaderHandler delegate can specify
a method to invoke to take action on the message headers before the object in
the body is deserialized. The delegate can be null.
Both versions read SOAP messages from the current position of
a stream and recreate the object graph from the message body. One lets us
register a call-back method to process SOAP headers in the message. We will
focus on the simple case without headers first. Later on section section 13.4
will show us how we can add SOAP Headers to a message and retrieve them with a HeaderHandler
delegate.
Let’s apply our newly acquired knowledge about the Deseralize()
method to write the SoapMessageReceiver
class to read SOAP messages from a message queue and extract the objects within
the message. The design of the receiver class is very similar to the design of
the SoapObjectSender
class from the previous section. It encapsulates all the details of message
queuing and serialization. A receiver object listens on one and the same queue
for its lifetime and keeps a formatter object to extract the objects in the
received objects. The next code listing shows the receiver class.
Listing 13.5 The receiver class processes SOAP messages
from a message queue
public class SoapObjectReceiver
{
protected MessageQueue
_ReceivingQueue;
protected SoapFormatter
_Formatter;
public
SoapMessageReceiver(string queueName)
{
_ReceivingQueue = new
MessageQueue(queueName);
_Formatter = new
SoapFormatter();
}
private void
ProcessStream( Stream stream )
{
IQueuedJob job =
_Formatter.Deserialize( stream ) as IQueuedJob;|#1
if( null != job )
{
job.Run();
}
}
public void
ReceiveObject( int timeout )
{
Message queueMessage;
try
{
queueMessage =
_ReceivingQueue.Receive(
new TimeSpan( 0,
0, 0, timeout, 0 ) );
Console.WriteLine(
"Message received." );
ProcessStream( queueMessage.BodyStream );
}
catch
(MessageQueueException ex)
{
// Handle no message
arriving in the queue.
if (
MessageQueueErrorCode.IOTimeout
!=
ex.MessageQueueErrorCode )
{
Console.WriteLine(
ex.Message );
}
}
} // ReceiveObject()
} // class SoapMessageReceiver
(annotation) <#1 Reconstruct the object graph with the
Deserialize() method. >
The majority of the code for this class is related to message
queuing, not parsing the message or the deserialization. That work is already
done for us within the Deserialize()
method of the SoapFormatter.
We do not need to provide any additional information to the method, because Serialize()
embedded all information necessary to recreate an object graph within the
message. Once Deserialize()
reads the type information, the SoapFormatter loads the required assembly, if it is not
already loaded, creates an object and initializes the object’s members with the
classes in the System.Reflection
namespace of the .NET Framework. Let’s examine each of these steps closer to
gain better understanding what goes on inside the Deserialize() method.
The first step deserializing an object is to resolve the
assembly information in the SOAP message. By default the SoapFormatter will store the fully
qualified assembly name, containing culture, version, and the strong name key
token in the message. Deserialize()
will attempt to locate an assembly that is compatible with the information in
the SOAP message. Including all these different pieces of information in the
compatibility check protects us from versioning problems. It ensures that the SoapFormatter
deserializes an object only if it can load an assembly compatible with the one
the object was serialized from. The price we pay for this protection is very
tight configuration management to keep all machines that serialize and
deserialize our messages on compatible assembly versions. If we are absolutely
confident that we want (or have) to trade in some risk of versioning problems
for more flexibility we can instruct the SoapFormatter to only persist a
simple assembly name. When we set the AssemblyFormat property to FormatterAssemblyStyle.Simple
the generated messages identify assemblies only by their name. The SoapFormatter
does not consider culture, version and strong name to determine compatibility.
With this setting a namespace attribute in listing 13.4 would be reduced to:
Table Values of the FormatterAssemblyStyle enumeration for
the AssemblyFormat.
FormatterAssemblyStyle Value
Description
Simple
The assembly description consists of the assembly name
only.
Full
The assembly description includes assembly name, culture,
version and public key token.
It is important for us to know that we have to set the AssemblyFormat
property to the same value on both, the serializing and the deserializing
formatter because Deserialize()
will default to the more restrictive behavior. It will always consider the
fully qualified name to load an assembly if it is present in the message even
if we set the AssemblyFormat
property to FormatterAssemblyStyle.Simple.
With the AssemblyFormat
property set to FormatterAssemblyStyle.Full
the SoapFormatter
will not process a message unless it contains the full type information.
Step two after loading the required assembly is to obtain a Type object for
the object we are going to deserialize. With the information and the Type’s name and
its .NET namespace from the SOAP message Deserialize() can query the assembly
for the Type
object through reflection. The SoapFormatter then instantiates an object from the Type and finally
initializes all fields from the data in the message.
The SoapFormatter
offers a hook to control locating types and assemblies, if we ever need to
change the default resolution for assemblies and types. We can assign a custom SerializationBinder
object that will take over loading assemblies and locating types. A custom SerializationBinder
must inherit from the abstract SerializationBinder class and override the BindToType()
method.
Table 13.5 A SerializationBinder has to implement the
BindToType() method. The method locates assemblies and resolves type names.
Method
Description
public abstract Type
BindToType(
string assemblyName,
string typeName
)
Resolves assembly and
type names and returns the Type object after a successful resolution.
If we register a custom binder with a formatter, it will call
the binder’s BindToType()
to obtain a Type
object for each object in the processed SOAP message. This is our chance to
substitute assemblies or types that are not available because they were retired
from the system, for example, or because we refactored classes into different
namespaces or assemblies or because we do not have access to the original
assemblies. In all these scenarios we can implement BindToType() to map an assembly
and/or a type name to a different assembly or type. Once we determined what
type to bind to our BindToType()
implementation has to return the corresponding Type object to the formatter. The formatter
then creates an object from that Type and proceeds as normal.
Step three in the deserialization process is the
reconstruction of the object graph. The SoapFormatter is facing some
interesting problems now: Which constructor am I going to use for classes with
more than one? Are there any side-effects from running a constructor that are
not compatible with the serialized field values? What parameter values am I
going to provide to a parameterized constructor? Because the formatter has no
way to determine the correct solutions, it solves this problem by not using any
constructor to create the object. It bypasses all the means of the .NET
Framework for object instantiation and does some low-level magic to create a
completely un-initialized object. Then it sets all the fields according to the
information found in the SOAP message through the classes in the System.Reflection
namespace.