Service Station

WCF Messaging Fundamentals

Aaron Skonnard

Code download available at: Service Station 2007_04.exe(161 KB)

Contents

XML Representations
The Message Class
Message Versions
Reading and Writing Messages
Typed Message Bodies
Message Lifetime
Message Headers and Properties
Mapping Messages to Methods
Endpoints and Bindings
Bringing It All Together

When you start pulling the layers away from Windows® Communication Foundation, you find a sophisticated XML-based messaging framework that offers a world of possibilities when connecting systems using a variety of protocols and formats. In this month's column, I am highlighting some of the key messaging features we can thank for such flexibility.

This column assumes a basic understanding of the Windows Communication Foundation programming model. If you're new to it, before proceeding you should read my article in the February 2006 issue of MSDN®Magazine.

One of the primary goals of the Windows Communication Foundation messaging architecture is to provide a unified programming model while also allowing flexibility in how data is represented and messages are transmitted. It accomplishes this by building on XML as the data model plus SOAP and WS-Addressing as the messaging framework. However, the fact that Windows Communication Foundation builds on these models doesn't mean you have to use XML 1.0, SOAP, or WS-Addressing when transmitting messages. You'll see that Windows Communication Foundation provides a great deal of flexibility.

XML Representations

Since the early days of XML, the software industry has relied on a little-known specification that provides a standard definition for the data found in an XML document. This specification, referred to as the XML Information Set (InfoSet), defines elements and attributes in terms of the information they contain, in a way that's completely independent of the byte representation. (See www.w3.org/TR/xml-infoset for details.)

The InfoSet specification has made it possible for other XML specifications and APIs to offer a consistent view of the data found in an XML document even though they might represent that data completely differently. Ultimately, the InfoSet provides a common rendezvous point for applications working with XML data, as illustrated in Figure 1. In the end, it's the job of an XML processor to translate between the byte representation and the programming model experience.

Figure 1 The Role of the XML InfoSet

Figure 1** The Role of the XML InfoSet **(Click the image for a larger view)

Windows Communication Foundation introduces some fundamental enhancements to the System.Xml namespace that make it possible to leverage alternate byte representations-rather than simply literal XML 1.0-when reading and writing XML documents. The main classes of interest here, System.Xml.XmlDictionaryReader and System.Xml.XmlDictionaryWriter, are located in the new System.Runtime.Serialization assembly that ships with the Microsoft® .NET Framework 3.0.

Both the XmlDictionaryReader and XmlDictionaryWriter classes provide static factory methods for creating readers and writers that use text, binary, and MTOM (Message Transmission Optimization Mechanism) representations. For instance, XmlDictionaryReader provides the CreateTextReader, CreateBinaryReader, and CreateMtomReader methods while XmlDictionaryWriter provides the corresponding CreateTextWriter, CreateBinaryWriter, and CreateMtomWriter methods.

Let's look at some examples. Consider this literal XML 1.0 representation of a customer:

<!-- customer.xml -->
<Customer xmlns="https://example.org/customer">
   <Email>bob@contoso.com</Email>
   <Name>Bob</Name>
</Customer>

The following code shows how to read customer.xml into an XmlDocument object and how you can use XmlDictionaryWriter to persist the same XmlDocument object back out to a binary representation on disk:

// read from XML 1.0 text representation
XmlDocument doc = new XmlDocument();
doc.Load("customer.xml");

// write to binary representation
FileStream custBinStream = new FileStream(
    "customer.bin", FileMode.Create);
using (XmlWriter xw = XmlDictionaryWriter.CreateBinaryWriter(
    custBinStream))
{
    doc.WriteContentTo(xw);
}

After this code runs, you'll have a binary representation saved in customer.bin that looks like Figure 2. Even though the binary editor shown in Figure 2 happens to display a sequence of characters, this is actually a binary file and obviously does not look like an XML 1.0 file. Nevertheless, it does represent an XML document (an InfoSet). Still, this binary format is proprietary to version 3.0 of System.Xml and is not compatible with other .NET versions or Web service frameworks.

Figure 2 Binary Representation of Customer XML Document

Figure 2** Binary Representation of Customer XML Document **(Click the image for a larger view)

The code sample in Figure 3 demonstrates how you can read the binary representation back into an XmlDocument object and how you can persist it back out to an MTOM representation on disk.The MTOM representation looks very different than the text or binary representations, as you can see in Figure 4. Nevertheless, it represents the same XML document and can be processed using the same System.Xml APIs. The MTOM representation also differs from the binary representation in that MTOM is a W3C Recommendation based on the InfoSet and is widely supported across Web service frameworks. MTOM makes it possible to optimize the transmission of binary elements within XML documents by leveraging multi-part MIME framing.

Figure 3 Read from Binary and Write to MTOM Representation

// read from binary representation
XmlDocument doc = new XmlDocument();
FileStream custBinStream = new FileStream(
    "customer.bin", FileMode.Open);
using (XmlReader xr = XmlDictionaryReader.CreateBinaryReader(
    custBinStream, XmlDictionaryReaderQuotas.Max))
{
    doc.Load(xr);
}

// write to MTOM representation
FileStream custMtomStream = new FileStream(
    "customer.mtom", FileMode.Create);
using (XmlWriter xw = XmlDictionaryWriter.CreateMtomWriter(
    custMtomStream, Encoding.UTF8, 1024, "text/xml"))
{
    doc.WriteTo(xw);
}

Figure 4 MTOM Representation of Customer XML Document

Figure 4** MTOM Representation of Customer XML Document **(Click the image for a larger view)

We can now come full circle by reading the MTOM representation back into an XmlDocument and by persisting it to a text representation on disk, like this:

// read from MTOM representation
XmlDocument doc = new XmlDocument();
FileStream custMtomStream = new FileStream(
    "customer.mtom", FileMode.Open);
using (XmlReader xr = XmlDictionaryReader.CreateMtomReader(
    custMtomStream, Encoding.UTF8, XmlDictionaryReaderQuotas.Max))
{
    doc.Load(xr);
}

// write to text (XML 1.0) representation
doc.Save("customer.xml");

The resulting custom.xml file should look identical to the original text representation. As you have seen, these System.Xml enhancements offer great flexibility by providing three ways to represent XML for persistence and transmission. Of course, other representations could be added in the future. And although it was Windows Communication Foundation that brought these enhancements to the table, you can make use of them in any .NET Framework 3.0 applications.

Windows Communication Foundation builds on this by letting you choose which XML representation to use when transmitting messages. If you need interoperability, you should choose the XML 1.0 text representation. If you need interoperability along with efficient support for binary payloads, you should choose the MTOM representation. And in scenarios that are .NET-only, the binary representation may provide better performance. The key here is that you actually have a choice.

The Message Class

Another key feature of any messaging framework is the ability to extend message payloads with arbitrary headers. A header is simply extra information traveling with the message, used to implement additional message-processing functionality (like security, reliable messaging, and transactions). In the case of XML messages, this means extending an XML payload with XML headers-both both of these are represented as XML elements framed within a container element. This functionality is exactly what SOAP provides.

The SOAP framework makes it possible to define XML-based protocols that can be used over any transport, but without relying on any transport-specific feature. WS-Addressing is a specification that extends SOAP to provide a transport-neutral mechanism for addressing/routing SOAP messages. Both SOAP and WS-Addressing are based on the InfoSet and permit (but do not require) XML 1.0 syntax to be used when transmitting messages.

The Windows Communication Foundation supports the use of any of the XmlDictionaryReader/Writer representations discussed in the previous section, which allows applications to seamlessly span a range of reach and performance requirements. Windows Communication Foundation models all messages using the Message class shown in Figure 5. As you can see, the Message class essentially models a message body and collections of message headers and properties. The available methods are primarily for creating messages, reading and writing the message body, and manipulating the collections of headers and properties.

Figure 5 System.ServiceModel.Channels.Message Class

public abstract class Message : IDisposable
{
    // numerous overloads for creating messages
    public static Message CreateMessage(...);

    // creates a buffered copy of a message
    public MessageBuffer CreateBufferedCopy(int maxBufferSize);

    // closes a message
    public void Close();

    // returns an XmlDictionaryReader for reading the body
    public XmlDictionaryReader GetReaderAtBodyContents();

    // deserializes the body into a .NET object
    public T GetBody<T>();
    public T GetBody<T>(XmlObjectSerializer serializer);

    // numerous methods/overloads for writing messages
    public void WriteMessage(XmlDictionaryWriter writer);
    public void WriteBody(XmlDictionaryWriter writer);
    public override string ToString();

    // properties
    public abstract MessageHeaders Headers { get; }
    public abstract MessageProperties Properties { get; }
    public MessageState State { get; }
    public abstract MessageVersion Version { get; }
    public virtual bool IsEmpty { get; }
    public virtual bool IsFault { get; }

    ... 
}

You create a Message object by calling one of the various static CreateMessage overloads and you dispose of a Message object using IDisposable or by explicitly calling Close. You can create a new Message from scratch, which is typical when sending a message. Or you can create a new Message from a message stream, which is typical when receiving messages.

In the case of creating a message from scratch, you must specify the action, the message version, and the body to use within the message. The action uniquely identifies the intent or semantics of the message. Windows Communication Foundation services rely on the action to dispatch incoming messages to the appropriate method. The message version identifies which versions of SOAP and WS-Addressing to use during transmission, if any. There are a variety of options to choose from when specifying the message version. Let's take a look at these options.

Message Versions

As mentioned, the MessageVersion class lets you specify the versions of SOAP and WS-Addressing you wish to use. You can create a MessageVersion object by calling CreateVersion and supplying an EnvelopeVersion object (to identify the SOAP version) along with an AddressingVersion object (to identify the WS-Addressing version). Here's what this looks like:

MessageVersion version = MessageVersion.CreateVersion(
    EnvelopeVersion.Soap12, AddressingVersion.WSAddressing10);

If you take a look at the EnvelopeVersion class, you'll see that Windows Communication Foundation currently supports three options for SOAP-None, Soap11, and Soap12, as shown here:

public sealed class EnvelopeVersion
{
    public static EnvelopeVersion None { get; }
    public static EnvelopeVersion Soap11 { get; }
    public static EnvelopeVersion Soap12 { get; }
    ... 
}

Likewise, if you look at AddressingVersion, you'll see that Windows Communication Foundation currently supports three options for WS-Addressing-None, WSAddressing10, and WSAddressingAugust2004, as shown here:

public sealed class AddressingVersion
{
    public static AddressingVersion None { get; }
    public static AddressingVersion WSAddressing10 { get; }
    public static AddressingVersion WSAddressingAugust2004 { get;}
    ... 
}

EnvelopeVersion.None specifies that you don't want to use SOAP during transmission, which also requires you to use AddressingVersion.None. This is a common setting for when you want to leverage Windows Communication Foundation in plain old XML messaging scenarios. Soap11 represents the SOAP 1.1 specification, which is in wide use today, and Soap12 represents the SOAP 1.2 W3C Recommendation (see www.w3.org/TR/soap for links to both specifications).

WSAddressingAugust2004 represents the original WS-Addressing W3C submission from August 2004 (see www.w3.org/Submission/ws-addressing), which is widely supported today. And WSAddressing10 represents the final WS-Addressing 1.0 W3C Recommendation (see www.w3.org/2002/ws/addr).

So, depending on which versions of SOAP and WS-Addressing the other side of the wire supports, you can pick and choose the right version to facilitate interoperability. And in order to make things a little easier for you, the MessageVersion class provides several public properties that return cached MessageVersion objects representing the most common combinations of both specifications (see Figure 6). So instead of calling CreateVersion explicitly, you can simply specify MessageVersion.Soap12WSAddressing10, which happens to be the same as MessageVersion.Default.

Figure 6 Cached MessageVersion Objects

public sealed class MessageVersion
{
    public static MessageVersion Default { get; }
    public static MessageVersion None { get; }
    public static MessageVersion Soap11 { get; }
    public static MessageVersion Soap11WSAddressing10 { get; }
    public static MessageVersion Soap11WSAddressingAugust2004 
      { get; }
    public static MessageVersion Soap12 { get; }
    public static MessageVersion Soap12WSAddressing10 { get; }
    public static MessageVersion Soap12WSAddressingAugust2004 
      { get; }

    ... // 
}

Reading and Writing Messages

When you create a message from scratch, there are a great many overloads that let you specify the body of the message. You can supply the body as an XmlDictionaryReader, XmlReader, or serializable object. You can also supply a MessageFault object for the body in order to create a fault message. Once the message has been created, you can gain access to the body by calling GetReaderAtBodyContents, which returns an XmlReader, or by calling GetBody<T> to deserialize the body into a .NET object.

When you want to write a message, you can write either the entire message or just the body by calling the WriteMessage or WriteBody methods, respectively. Both have overloads that allow you to provide an XmlDictionaryWriter or XmlWriter object. The following example shows how to create a new message that's subsequently written to a file named message.xml:

// load body from customer.xml file
XmlDocument doc = new XmlDocument();
doc.Load("customer.xml");

Message m = Message.CreateMessage(
    MessageVersion.Soap11WSAddressingAugust2004, 
    "urn:add-customer", new XmlNodeReader(doc));

FileStream fs = new FileStream("message.xml", FileMode.Create);
using (XmlWriter xw = XmlDictionaryWriter.CreateTextWriter(fs))
{
    m.WriteMessage(xw);
}

In this case, "urn:add-customer" is specified for the action and Soap11WSAddressing2004 for the message version. The body is supplied via an XmlReader. And then WriteMessage is called to write the message out to a text-based XmlDictionaryWriter object. The resulting message.xml looks like this:

<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:a="https://schemas.xmlsoap.org/ws/2004/08/addressing">
  <s:Header>
    <a:Action s:mustUnderstand="1">urn:add-customer</a:Action>
  </s:Header>
  <s:Body>
    <Customer xmlns="https://example.org/customer">
      <Email>bob@contoso.com</Email>
      <Name>Bob</Name>
    </Customer>
  </s:Body>
</s:Envelope>

Note that the XML namespaces indicate SOAP 1.1 and WS-Addressing from August 2004. It's important to point out that you could easily write the message out to a binary or MTOM representation using a different XmlDictionaryWriter object.

Now, if you wanted to, you could change the version to MessageVersion.Soap12WSAddressing10, and the resulting file would look like this instead:

<s:Envelope xmlns:s="https://www.w3.org/2003/05/soap-envelope" 
    xmlns:a="https://www.w3.org/2005/08/addressing">
  <s:Header>
    <a:Action s:mustUnderstand="1">urn:add-customer</a:Action>
  </s:Header>
  <s:Body>
    <Customer xmlns="https://example.org/customer">
      <Email>bob@contoso.com</Email>
      <Name>Bob</Name>
    </Customer>
  </s:Body>
</s:Envelope>

Notice the newer versions of SOAP and WS-Addressing.

And if you were to change the version to MessageVersion.None, the resulting message would no longer contain a SOAP envelope. Instead, it would simply contain the body:

<Customer xmlns="https://example.org/customer">
  <Email>bob@contoso.com</Email>
  <Name>Bob</Name>
</Customer>

In order to read the message back in, you need to use a different CreateMessage overload that allows you to provide an XmlDictionaryReader for reading the message stream. You also need to specify the correct message version. The code that follows shows an example that reads the last message above with no envelope (version is MessageVersion.None):

FileStream fs = new FileStream("message.xml", FileMode.Open);
using (XmlDictionaryReader reader = 
   XmlDictionaryReader.CreateTextReader(
      fs, XmlDictionaryReaderQuotas.Max))
{
    Message m = Message.CreateMessage(reader, 1024, 
       MessageVersion.None);

    XmlDocument doc = new XmlDocument();
    doc.Load(m.GetReaderAtBodyContents());
    Console.WriteLine(doc.InnerXml);
}

As you can see, the Message class provides great flexibility around reading and writing messages using different versions of SOAP and WS-Addressing-if using any of them at all. Also, the fact that it builds on XmlDictionaryReader for reading and writing operations means you can use any of the supported XML representations when transmitting messages.

Typed Message Bodies

So far, I've been showing examples of working directly with XML. When you'd rather deserialize XML into .NET objects, you can use one of the supported serializers, such as DataContractSerializer, NetDataContractSerializer, or XmlSerializer. The following class is capable of representing the XML found in customer.xml when used with DataContractSerializer:

[DataContract(Namespace="https://example.org/customer")]
public class Customer
{
    public Customer() { }
    public Customer(string name, string email)
    {
        this.Name = name;
        this.Email = email;
    }
    [DataMember]
    public string Name;
    [DataMember]
    public string Email;
}

Now you can create messages by supplying Customer objects for the body, like this:

Customer cust = new Customer("Bob", "bob@contoso.com");
Message msg = Message.CreateMessage(
    MessageVersion.Soap11WSAddressingAugust2004, 
    "urn:add-customer", cust);

Despite using an object to represent the body, the resulting message will look identical to the previous examples.

Now, when reading the body, you can use GetBody<Customer> to produce a new Customer object, as shown here:

Customer c = msg.GetBody<Customer>();
Console.WriteLine("Name: {0}, Email: {1}", c.Name, c.Email);

These versions of CreateMessage and GetBody<T> use DataContractSerializer behind the scenes. There is another version of each method that allows you to explicitly specify the serializer you wish to use.

For more details, see my August 2006 column titled "Serialization in Windows Communication Foundation" (available at msdn.microsoft.com/msdnmag/issues/06/08/ServiceStation).

Message Lifetime

The Message class has been carefully designed to support streaming. As a consequence, the body can only be processed once during the lifetime of a Message object. In order to ensure this, the Message class provides a State property that describes the current state of the object. MessageState defines five possible message states:

public enum MessageState
{
    Created,
    Read,
    Written,
    Copied,
    Closed
}

A Message object starts out in the Created state, which is the only valid state for processing the body. There are a few different ways to process the body: you can read it, write it, or copy it. Calling either GetReaderAtBodyContents or GetBody<T> changes the state to Read. Calling either WriteMessage or WriteBody changes the state to Written. And calling CreateBufferedCopy changes the state to Copied. For example, consider the following example that moves a message through several states:

Customer cust = new Customer("Bob", "bob@contoso.com");
Message m = Message.CreateMessage(
    MessageVersion.Soap11WSAddressingAugust2004, 
    "urn:add-customer", cust);
Console.WriteLine("State: {0}", m.State);

Customer c = m.GetBody<Customer>();
// cannot access body from here on
Console.WriteLine("State: {0}", m.State);

m.Close();
Console.WriteLine("State: {0}", m.State);

When you execute the code, this text is printed to the console:

State: Created
State: Read
State: Closed

Once a Message object is no longer in the Created state, any method that needs access to the body will throw an exception. For example, calling GetBody<T> again, after the first call results in an exception. In situations where you need to process the body multiple times, you can create a buffered copy (by calling CreateBufferedCopy) or you can load the body into an XmlDocument or deserialize it into a .NET object for in-memory use.

Needless to say, the State property makes it possible to determine at runtime if the body of a particular Message has already been consumed (when it's no longer in the Created state) and how it was consumed (whether it was read, written, or copied).

Message Headers and Properties

The Message class provides a Headers property to model the collection of headers associated with the body. The following example illustrates how to add a custom header named "ContextId" to a message:

Customer cust = new Customer("Bob", "bob@contoso.com");
Message m = Message.CreateMessage(
    MessageVersion.Default, "urn:add-customer", cust);

m.Headers.Add(
    MessageHeader.CreateHeader(
        "ContextId", "https://example.org/customHeaders", 
        Guid.NewGuid()));

When this message is written, it looks like this:

<s:Envelope xmlns:s="https://www.w3.org/2003/05/soap-envelope" xmlns:a="https://www.w3.org/2005/08/addressing">
  <s:Header>
    <a:Action s:mustUnderstand="1">urn:add-customer</a:Action>
    <ContextId xmlns="https://example.org/customHeaders"
    >a200f76d-5b83-4496-a035-4f9e70a07959</ContextId>
  </s:Header>
  ...
</s:Envelope>

It's typically the job of intermediaries to process headers found in a message. Such intermediaries often need to store the results of their processing somewhere for future use in the processing pipeline. For this purpose, the Message class also provides a Properties collection for storing arbitrary name/object pairs.

Unlike headers, which are always transmitted in the message, message properties are typically only used during local processing-they may not have any impact on what happens on the wire, although sometimes they do. For example, when using HTTP, Windows Communication Foundation stores the HTTP request details in the Properties collection of the incoming Message object. The details are stored in an object of type HttpRequestMessageProperty, which you can access using httpRequest for the property name. You can also influence the HTTP response details by populating a property named httpResponse with an object of type HttpReponseMessageProperty.

It's common for components within the Windows Communication Foundation channel layer to make heavy use of headers and properties-especially the built-in protocol channels that implement the various WS-* specifications.

Mapping Messages to Methods

Before you can begin sending or receiving messages with Windows Communication Foundation, you must first define a service contract that maps incoming messages to methods. This is accomplished by associating action values with method signatures. For example, the following interface defines the most universal service contract possible for a one-way operation:

[ServiceContract]
public interface IUniversalOneWay
{
    [OperationContract(Action="*")]
    void ProcessMessage(Message msg);
}

This definition associates ProcessMessage with all incoming messages regardless of their action values (thanks to the Action= "*" clause). It's also possible to define a generic request-reply operation using the same technique:

[ServiceContract]
public interface IUniversalRequestReply
{
    [OperationContract(Action="*", ReplyAction="*")]
    Message ProcessMessage(Message msg);
}

You can also associate methods with specific action values by setting the Action property to a specific value like urn:add-customer, as shown here:

[ServiceContract]
public interface ICustomer
{
    [OperationContract(Action="urn:add-customer")]
    void AddCustomer(Message msg);
}

When Windows Communication Foundation receives an incoming message, it looks at the action to determine which method to dispatch to and then it performs any necessary serialization. When using Message for the request/response types, Windows Communication Foundation doesn't perform any serialization-you decide how to process the message. However, when using typed signatures, Windows Communication Foundation automates the process of reading and writing the message bodies behind the scenes using serialization techniques. For example, consider this service contract:

[ServiceContract]
public interface ICustomer
{
    [OperationContract(Action="urn:add-customer")]
    void AddCustomer(Customer cust);
}

In this case, Windows Communication Foundation takes care of deserializing the body of the incoming message into a Customer object before invoking AddCustomer.

There is another serialization shortcut, known as message contracts, for automatically adding headers to a Message object. Message contracts allow you to annotate a class, specifying which fields map to headers versus the body:

[MessageContract(IsWrapped=false)]
public class AddCustomerRequest
{
    [MessageHeader(Name="ContextId", 
        Namespace="https://example.org/customHeaders"]
    public Guid ContextId;

    [MessageBodyMember]
    public Customer customer;
}

With this message contract class in place, you can define an operation that uses it as the request type, as shown here:

[ServiceContract]
public interface ICustomer
{
    [OperationContract(Action = "urn:add-customer")]
    void AddCustomer(AddCustomerRequest request);
}

This tells Windows Communication Foundation to automatically map between the ContextId field in the object and the ContextId header in the SOAP message, freeing you from having to manually deal with the Headers collection.

Endpoints and Bindings

In order to wire-up a Windows Communication Foundation service, you need to provide several pieces of information. First, you must tell it what transport and address to use. Second, you must specify what XML representation and message version to expect. Third, you must configure which WS-* protocols to wire-in. And finally, you must provide the mapping between the incoming messages and the service methods. In Windows Communication Foundation, you specify all of these details via endpoints.

An endpoint configuration is simply a combination of an address, binding, and contract. As I already discussed, the contract defines the mapping between messages and methods. The binding specifies the remaining messaging details. It specifies what transport, XML representation, and message version to use during transmission. It also identifies which WS-* protocols should be included when building the channel stack.

A binding specifies a message encoder; this is what actually controls the XML representation and message version details. At run time, the message encoder is the component used by the transport channel to read and write messages to and from a stream. Windows Communication Foundation provides three built-in message encoder implementations: TextMessageEncoder, BinaryMessageEncoder, and MtomMessageEncoder. All of these classes build on the new XmlDictionaryReader and XmlDictionaryWriter classes discussed earlier to support a given XML representation. Each class also comes configured to use a specific message version.

As you may know, Windows Communication Foundation comes with built-in bindings to facilitate common scenarios. Each binding is configured to use a particular message encoder. For example, both BasicHttpBinding and WSHttpBinding are configured to use the TextMessageEncoder. However, the former uses MessageVersion.Soap11 while the latter uses MessageVersion.Soap12WSAddressing10 for the message version. NetTcpBinding, NetNamedPipeBinding, and NetMsmqBinding all come configured to use the BinaryMessageEncoder and MessageVersion.Soap12WSAddressing10.

You can opt to use a different message encoder on a particular binding through its properties or a binding configuration. For example, the following shows how to change BasicHttpBinding's message encoder to MtomMessageEncoder:

BasicHttpBinding basic = new BasicHttpBinding();
basic.MessageEncoding = WSMessageEncoding.Mtom;
... // use binding 

When you need more control over the binding configuration, you can define a custom binding using the CustomBinding class:

MtomMessageEncodingBindingElement mtom = 
    new MtomMessageEncodingBindingElement(
        MessageVersion.Soap12, Encoding.UTF8);

CustomBinding binding = new CustomBinding();
binding.Elements.Add(mtom);
binding.Elements.Add(new HttpTransportBindingElement());
... // use binding 

This technique allows you to configure the precise message version and text encoding details used by the message encoder. In addition to this flexibility, the Windows Communication Foundation architecture lets you write and use custom message encoders-the SDK comes with a sample that shows how to do this. In general, Windows Communication Foundation makes it easy to configure endpoints to use different XML representations and message versions without having any impact on your service code.

Bringing It All Together

Let's look at a final example that brings most of these messaging concepts together. Figure 7 shows how to implement, host, and configure a generic service that uses the IUniversalOneWay contract along with a customized binding and encoder. Figure 8 shows how to implement a client capable of sending messages to the hosted service. The service implementation uses System.Xml to process the incoming message while the client produces a message using a Customer object.

Figure 8  Generic WCF Client

class Program
{
    static void Main(string[] args)
    {
        Console.ReadLine();

        // create and configure an MTOM encoder
        MtomMessageEncodingBindingElement mtom = 
            new MtomMessageEncodingBindingElement(
                MessageVersion.Soap12, Encoding.UTF8);

        // create a CustomBinding using MTOM encoder
        CustomBinding binding = new CustomBinding();
        binding.Elements.Add(mtom);
        binding.Elements.Add(new HttpTransportBindingElement());

        // create a ChannelFactory using the custom binding
        ChannelFactory<IUniversalOneWay> cf = 
            new ChannelFactory<IUniversalOneWay>(binding);
        IUniversalOneWay channel = cf.CreateChannel(
            new EndpointAddress(
                "https://localhost:8080/customerservice"));

        // create a Message and send through channel
        Customer cust = new Customer("Bob", "bob@contoso.com");
        Message msg = Message.CreateMessage(
            MessageVersion.Soap12, "urn:add-customer", cust);
        channel.ProcessMessage(msg);
    }
}

Figure 7 Generic WCF Service

public class CustomerService : IUniversalOneWay
{
    #region IUniversalOneWay Members
    public void ProcessMessage(Message msg)
    {
        Console.WriteLine("Message received...");
        XmlDocument doc = new XmlDocument();
        doc.Load(msg.GetReaderAtBodyContents());
        Console.WriteLine("Customer: {0} ({1})",
            doc.SelectSingleNode("/*/*[local-name()='Name']").InnerText,
            doc.SelectSingleNode("/*/*[local-name()=
                'Email']").InnerText); 
    }
    #endregion
}

class Program
{
    static void Main(string[] args)
    {
        using (ServiceHost host = 
            new ServiceHost(typeof(CustomerService),
            new Uri("https://localhost:8080/customerservice")))
        {
            // create and configure an MTOM encoder
            MtomMessageEncodingBindingElement mtom = 
                new MtomMessageEncodingBindingElement(
                    MessageVersion.Soap12, Encoding.UTF8);

            // create a CustomBinding using MTOM encoder
            CustomBinding binding = new CustomBinding();
            binding.Elements.Add(mtom);
            binding.Elements.Add(
                new HttpTransportBindingElement());

            // add an endpoint using the custom binding
            host.AddServiceEndpoint(typeof(IUniversalOneWay), 
                binding, "");
            host.Open();

            Console.WriteLine("CustomerService is up and running...");
            Console.ReadKey();
        }
    }
}

The Windows Communication Foundation messaging architecture provides a unified programming model that offers significant messaging flexibility. This makes it possible to seamlessly implement plain-old XML services that don't use SOAP, WS-Addressing, or any other WS-* protocol. It also makes it possible to configure the more sophisticated bindings to meet the precise needs of specific integration scenarios when different protocols and versions are required.

Send your questions and comments for Aaron to  sstation@microsoft.com.

Aaron Skonnard is a cofounder of Pluralsight, a Microsoft .NET training provider. Aaron is the author of Pluralsight's Applied Web Services 2.0, Applied BizTalk Server 2006, and Introducing Windows Communication Foundation courses. Aaron has spent years developing courses, speaking at conferences, and teaching professional developers.