IClientMessageFormatter– How to customize Messages on the client side

Posted on February 15, 2011 by

1


WCF allows client side developers to perform communication operations by using strongly typed operations. Internally, WCF translates these strongly typed operations into a generic Message class, which can be used by the WCF client runtime. When sending a message, WCF takes the parameters passed as the client operation parameters and “serializes” them into a Message object. When receiving a reply, WCF “deserializes” a Message object (received from the client runtime) into a return object.

Note that we are not talking about actually serializing messages into a stream of bytes to be sent over the network or desrializing stream of bytes received from the network and creating a Message out of them. This is done by encoders in the Channel Stack. We are talking about converting the strongly typed operations to an internal Message representation and vice versa.

Pipe

It is possible to hook into the serialization and deserialization operations and extend them as needed. This is done by implementing the IClientMessageFormatter interface and attaching it into the WCF pipeline.

This interface contains two methods:

public interface IClientMessageFormatter
{
    Message SerializeRequest(
                MessageVersion messageVersion,
                object[] parameters);

    object DeserializeReply(
                Message message,
                object[] parameters);
}

The SerializeRequest method is called when a message is send and WCF wants to serialize the operation parameters into a Message object. The DesrializeReply method is called when a reply (a Message object) was received from the client runtime layer and WCF wants to deserialize it into a return object.

Both methods gets a parameters array, however, the meaning of these arrays is different in each method. The parameters passed to the SerializeRequest method are the parameter values passed to the client operation, while the parameters array passed to the DesrializeReply method, are any out parameters provided to the client operation.

Implementing these extension gives you the freedom to customize the serialization and desrialization process, inspect the message content and its headers, etc. It is up to you to return a valid Message object or return value (by using an original formatter or doing it yourself, by calling the Message.CreateMessage() method). When creating a Message, it is possible to attach a custom serializer to the created message (this time I’m talking about a real serializer used to serialize\deserialize the message into\from a stream of bytes sent over the network). In the example below, we do just that.

Attaching this extension to the WCF pipeline is done by creating IOperationBehavior implementation (see https://wcfpro.wordpress.com/2010/12/22/ioperationbehavior/) as shown in the sample bellow.

IClientMessageFormatter  Sample

In the following sample we create a custom IClientMessageFormatter , which attaches a custom serializer. This formatter uses the XmlSerializer internally (as described in this post https://wcfpro.wordpress.com/2010/11/24/customxmlobjectserializer/ ) instead of the default System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter serializer.

Our contract contains one method with one parameter, and is marked with ‘IsOneWay=true’, which means no no reply is expected (just for simplicity):

    [ServiceContract]
    public interface IPing
    {
        [OperationContract(IsOneWay = true, Action = "*")]
        void Foo(MyRequest request);
    }

The MyRequest object (which we are about to serialize) is as follows:

    [XmlRoot("MyDemoRequest")]
    public class MyRequest
    {
        public string Name { get; set; }

        [XmlElement("MyDemoDescription")]
        public MyDescription Description { get; set; }
    }

    public class MyDescription
    {
        public double Version { get; set; }

        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public bool LastVersionSpecified { get; set; }

        public string Description { get; set; }
    }

Note that MyRequest class is not decorated with DataContract attributes, as we are not going to use the default DataContractSerializer.

Next, we define a MyFormatterBehavior class in order to attach our formatter into the WCF pipeline. The custom formatter is added through the ClientOperation parameter of the ApplyClientBehavior method. If we need access to the original formatter, we can keep it before we assign new formatter to the ClientOperation parameter.


    public class MyFormatterBehavior : IOperationBehavior
    {
        public void AddBindingParameters(
            OperationDescription operationDescription,
            BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(
            OperationDescription operationDescription,
            ClientOperation clientOperation)
        {
            clientOperation.Formatter = new MyXmlSerializerClientFormatter();
        }

        public void ApplyDispatchBehavior(
            OperationDescription operationDescription,
            DispatchOperation dispatchOperation)
        {
        }

        public void Validate(OperationDescription operationDescription)
        {
        }
    }

    internal class MyXmlSerializerClientFormatter :
        IClientMessageFormatter
    {
        private CustomXmlObjectSerializer m_xmlObjectSerializer;

        public MyXmlSerializerClientFormatter()
        {
            m_xmlObjectSerializer = new CustomXmlObjectSerializer(typeof(MyRequest));
        }

        public object DeserializeReply(Message message, object[] parameters)
        {
            // In this sample we defined our operations as OneWay, therefore, this method
            // will not get invoked.
            throw new NotSupportedException(
                "This formatter is meant to be used on OneWay operations...");
        }

        public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
        {
            Message message = null;
            // In our sample, we pass only one parameter to the operation
            Debug.Assert(parameters.Length == 1);

            // Serialize the body according to the first parameter
            message = Message.CreateMessage(
                messageVersion, string.Empty, parameters[0], m_xmlObjectSerializer);

            return message;
        }

    }

When inspecting the actual serialized message, we can see that our custom serializer was used instead of the original DataContractSerializer.

Default client formatter –

 <s:Body>
    <Foo xmlns="http://tempuri.org/">
      <request xmlns:a="http://schemas.datacontract.org/2004/07/WCFSample.service" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <a:Description>
          <a:Description i:nil="true"/>
          <a:LastVersionSpecified>false</a:LastVersionSpecified>
          <a:Version>5</a:Version>
        </a:Description>
        <a:Name>My demo request</a:Name>
      </request>
    </Foo>
  </s:Body>

Our Custom Formatter –

 <s:Body>
    <MyDemoRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <Name>My demo request</Name>
      <MyDemoDescription>
        <Version>5</Version>
      </MyDemoDescription>
    </MyDemoRequest>
 </s:Body>

Advertisements
Posted in: WCF Extensions