Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Indigo: Playing with the Message Class

Blogs

Mike Taulty's Blog

Elsewhere

Archives

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