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 :
320
Retrieving Message Headers
Retrieving the SOAP headers from a message is by far more
complex than adding them. This is mostly because the mechanism to retrieve
headers is designed for executing method calls through the .NET remoting
infrastructure rather than for transmitting serialized objects. Nevertheless we
will outline how to retrieve headers from messages like the ones we have seen
so far in this chapter. In chapter 14 we learn about creating SOAP messages in
RPC format instead of the document format and we will see a different technique
to access headers in a message.
We saw that one version of the Deserialize() method allows to
register a delegate to handle the header section of the SOAP message. Deserialize()
will call the registered method after it parsed the <SOAP-ENV:Header> section of
the message, but before it deserializes the content of the <SOAP-ENV:Body>.
Deserialize()
will pass the handler method an array of Header objects, very much like the
array we originally passed to Serialize() to produce the headers. We can iterate over
the Header
array and process each header entry one-by-one. By doing this we will find that
there is an extra element called __methodName in the array. It contains the name of the
first child element of the <SOAP-ENV:Body>.
Remember how I said, that reading headers is very much designed around the
parsing RPCs? Here is the first piece of evidence to back that statement. A
message in RPC format contains the name of the method to execute as the first
body element. In our scenario, however, this pseudo header does not help us at
all.
So much for the theory, let’s get back to the code example.
We need a class to process header entries and extract the transaction
identifier. We also have to check for other header entries because the SOAP
specs mandate that an application has to abort processing and discard a message
if it can not act on a required entry, i.e. an entry with the mustUnderstand
attribute set to “1”. Both of these requirements are implemented in the HeaderProcessor
class. This class exposes a single public method, HandleHeaders, to receive and process
the message headers. We are going to register HandleHeaders via a delegate with Deserialize() to
receives the headers. HandleHeaders
processes each header entry and throws an exception if it finds a required
entry it does not understand. When it finds the TransactionId header it stores ID’s
value in a public property for later access.
Listing Classes to process header entries from a SOAP
message.
public class HeaderException : ApplicationException
{
public HeaderException(
string msg ) : base(msg) {}
}
[Serializable]
public class HeaderProcessor : ISerializable
{
public object
HandleHeaders( Header[] headers ) |#1
{
foreach( Header h in
headers ) |#2
{ |
ProcessHeader(h); |
} |
return this; |#3
}
public int
TransactionId;
private void
ProcessHeader( Header header )
{
if( header.Name.CompareTo("TransactionId")
== 0 )
{
TransactionId =
(int)header.Value;
}
else if(
header.MustUnderstand == true )
|#4
{
|
throw new
HeaderException( String.Format(
|
"Don’t
understand {0}", header.Name) );
|
}
|
}
public HeaderProcessor
() {}
// Required for all
classes implementing ISerializable
public HeaderProcessor (
SerializationInfo info,
StreamingContext
context ) { /* empty */ }
// Method defined by
ISerializable
public void
GetObjectData( SerializationInfo info,
StreamingContext
context ) { /* empty */ }
}
(annotation) <#1 Register this method with the
SoapFormatter to process the headers of the message.>
(annotation) <#2 Process each header.>
(annotation) <#3 The header handler has to return an
object that implements ISerializable.>
(annotation) <#4 Abort processing if the message
contains any required headers that we do not understand .>
That was straight forward so far, now here comes the rough
part, rough mostly because we are operating a little bit outside the area of
intended use for HeaderHandlers.
Deserialize()
will not recreate the object in the message body when a HeaderHandler is registered. Instead
it will return the object returned from the HeaderHandler. Any attempt to recreate the serialized
object in our HandleHeaders()
method will fail, however, because we cannot access the type information. The
only information available about the type of the root object in the message is
the plain class name in the pseudo-header __methodName. Since we do not get any
information about the class’ namespace or its assembly, we are unable to create
an object of the correct type. That is another piece of evidence that HeaderHandlers
are only designed for deserializing RPC messages. SOAP RPC servers do not need
any type information to invoke a method call, because they only expose an
interface at a given URL.
The SoapFormatter
requires the HeaderHandler
to return an object that implements the ISerializable interface. After the HeaderHandler
exits the SoapFormatter
will call the deserialization constructor to initialize the object with before Deserialize()
returns it to the caller.
BUG WARNING: We have to return a valid object from the header
handler method. The SoapFormatter
will throw an exception if we return null (this
behavior is specific to the SoapFormatter, its sibling, the BinaryFormatter
behaves different.)
The SerializationInfo
passed into the deserialization constructor contains entries describing most of
the SOAP message, but it still does not provide enough information to reliably
reconstruct the root of the object graph. The entries in the SerializationInfo
object are listed.
Table The SerializationInfo passed to an object after
headers were processed with a HeaderHandler contains more data than just the
serialized object graph.
SerializationInfoEntry
Type
Description
__methodName
string
The name of the first element in the <Body>. In an
RPC messages it is the name of the invoked message. In a document message it
is the class name of the root of the serialized object graph.
__keyToNamespaceTable
Hashtable
A Dictionary of all attributes found in the message and
their values.
__paramNameList
ArrayList
The field names of the root object or parameter names of an
RPC call. Retrieve the values by these names from the SerializationInfo.
<field name>
object
The values of the fields listed in the __paramNameList.
__fault
At this point we should concede that a HeaderHandler was not intended to
reconstruct the root object of a serialized object graph. To work around this
limitation we can call Deserialize()
twice, once to retrieve and process the message headers and once to retrieve
the serialized object, but this solution carries a pretty severe performance
penalty.
We can finish this section with a modified version of the ReceiveObject()
method first shown in listing 13.5. It shows how we can call Deserialize()
once with the HeaderProcessor
class from listing 13.8 to process the message headers and a second time to
retrieve the object stored inside the message. Note that we have to reposition
to the stream to the beginning of the SOAP message after the first call to Deserialize()
because streams are always positioned after the last read position.
Listing A modified Receive() method to retrieve message
headers before processing the object
private void ProcessStream( Stream stream )
{
HeaderProcessor
processor = new HeaderProcessor();
IQueuedJob job =
_Formatter.Deserialize( stream ) as IQueuedJob;
if( null != job )
{
job.Run();
}
}
catch(HeaderException
ex) |#3
{ |
Console.WriteLine(
"Incompatible Headers:" ); |
Console.WriteLine(
ex.Message ); |
}
|
}
(annotation)
<#1 Call Deserialize once to retrieve the headers from the message. >
(annotation)
<#2 Read the message a second time to retrieve the object in the body,
because we cannot reliably deserialize it with the information in the context
object.>
(annotation) <#3 Do not process the message if we did
not understand mandatory headers in the message.>