Following up on previous Indigo postings (start
here),
programming at this low level in Indigo is all about manipulating
Message class instances.
The Message class is an interesting one - it has an object model that maps to
a SOAP message although whether a particular Message instance ends up on the
wire as a real "angle-bracket" SOAP message is going to depend on the Encoder
that puts it there.
So, what can you do with a Message? Well, the first thing that you can't do
is create one directly as it's an abstract base class. So, if you want to create
an instance you need to use Message.CreateMessage which will return some
concrete message type. There are 14 overloads here combining different ways of
setting the message version (SOAP 1.1 or SOAP 1.2), the Action and the way you
want to populate the body (e.g. XmlReader).
So creating a message might look like;
[DataContract]
class Point
{
public Point()
{
}
public Point(int x, int y)
{
_x = x;
_y = y;
}
[DataMember("x")]
private int _x;
[DataMember("y")]
private int _y;
}
class Program
{
static void Main(string[] args)
{
// Create a body from an object
Message m1 = Message.CreateMessage(
"urn:MyAction",
new Point(10, 20));
// Create a body from a reader
Message m2 = Message.CreateMessage(
"urn:MyAction",
new XmlTextReader(new
StringReader("<hello/>")));
m1.Close();
m2.Close();
}
}
Once we have a message (whether we created it or had it delivered to us from
a Channel), what can we do with it? Firstly, we can play around with the headers
and add/access instances of the
MessageHeader class.
class Program
{
static void Main(string[] args)
{
// Create a body from an object
Message m1 = Message.CreateMessage(
"urn:MyAction",
new Point(10, 20));
MessageHeader header =
MessageHeader.CreateHeader(
"MyHeader",
"urn:MyNamespace",
100.0, // Data to place in
header (object)
true); // Must understand
m1.Headers.Add(header);
}
}
Secondly, we can also add Properties to the message. The
MessageHeaders that we add are actually going to go out on the "wire" when
the message gets sent by the channel whereas the properties do not go out
on the wire. They're used as a state bag to pass information from between pieces
of code that are co-located on either the client or the service side.
class Program
{
static void Main(string[] args)
{
// Create a body from an object
Message m1 = Message.CreateMessage(
"urn:MyAction",
new Point(10, 20));
MessageHeader header =
MessageHeader.CreateHeader(
"MyHeader",
"urn:MyNamespace",
100.0, // Data to place in
header (object)
true); // Must understand
m1.Headers.Add(header);
// 2nd param here is of type object.
m1.Properties.Add("MyProperty",
"Property Value");
XmlTextWriter writer = new
XmlTextWriter(Console.Out);
writer.Formatting =
Formatting.Indented;
m1.WriteMessage(writer);
}
}
(I can also put a real object type (rather than a floating point 100.0) into
that header value and it would serialize based upon DataContract).
Once a message has been "read" we can't read it again. Messages have a
property State and go through a lifecycle of Created, Written, Read,
Copied, Closed.
The states of Created and Closed seem fairly obvious. Read and Written are
interesting. If I create a Message like I did above and then get the contents
back out using a method such as GetBodyObject then the Message is
considered Read. If I write the contents of the Message somewhere else
using a method such as WriteMessage then the Message is considered
Written. Either way, I won't be able to read or write that Message again -
it looks to be a one-shot read/write and then the Message is not re-readable or
re-writeable.
So, the following code is illegal and will throw;
class Program
{
static void Main(string[] args)
{
// Create a body from an object
Message m1 = Message.CreateMessage(
"urn:MyAction",
new Point(10, 20));
Console.WriteLine(m1.State); //
"Created"
XmlTextWriter writer = new
XmlTextWriter(Console.Out);
writer.Formatting =
Formatting.Indented;
m1.WriteMessage(writer);
Console.WriteLine(m1.State); //
"Written"
try
{
// This will throw -
message is already "consumed" in some sense
Point p =
m1.GetBody<Point>();
Console.WriteLine(m1.State);
}
catch
{
}
m1.Close();
Console.WriteLine(m1.State); //
"Closed"
}
}
If we want to play with a Message instance repeatedly then we need to use
Message's friend, the
MessageBuffer class to help out.
class Program
{
static void Main(string[] args)
{
// Create a body from an object
Message m1 = Message.CreateMessage(
"urn:MyAction",
new Point(10, 20));
XmlTextWriter writer = new
XmlTextWriter(Console.Out);
writer.Formatting =
Formatting.Indented;
MessageBuffer buffer =
m1.CreateBufferedCopy(0);
Console.WriteLine(m1.State); //
"Copied"
for (int i = 0; i < 3; ++i)
{
Message m2 =
buffer.CreateMessage();
Point p =
m2.GetBody<Point>();
m2.Close();
}
buffer.Close();
m1.Close();
}
}
Notice that the act of creating a buffered copy from a Message instance moves
the Message to a state of Copied and, again, consumes the Message so that
it can't be read, written or copied again. Note that I can call WriteMessage
straight from the MessageBuffer to write into a Stream without
having to create a Message instance like I do above.
The Message can also create SOAP faults for us as in;
class Program
{
static void Main(string[] args)
{
// Create a body from an object
MessageFault fault = MessageFault.CreateFault(
new FaultCode("Code"),
new FaultReason("Reason"));
Message m1 = Message.CreateMessage(fault);
XmlTextWriter writer = new
XmlTextWriter(Console.Out);
writer.Formatting = Formatting.Indented;
m1.WriteMessage(writer);
writer.Close();
m1.Close();
}
}
and we can then walk up to an instance and ask IsFault to determine
whether we have a "real" message or a fault.
That's it. It's not definitive by any means and there may be mistakes in there
but I was playing with it so shared it here.
Posted
Fri, Apr 8 2005 5:19 PM
by
mtaulty