[WCF]How to inspect and modify WCF message via custom MessageInspector

[WCF]How to inspect and modify WCF message via custom MessageInspector

Recently I received some questions about how to modify message content of WCF operation calls. By checking the WCF extensibility, it seems “MessageInspector” is the proper one for message content modification. Here is the description of “MessageInspector” from MSDN library:

A message inspector is an extensibility object that can be used in the service model's client runtime and dispatch runtime programmatically or through configuration and that can inspect and alter messages after they are received or before they are sent.

Though it mentioned both “inspect” and “alter”, the sample code provided only demonstrate how to inspect the message(help us build a simple message logger). By searching, I only find the following blog entry posted by Kirk Evans which gives us an example of modifying WCF message in messageInspector. Thanks Kirk.

#Modify Message Content With WCF

https://blogs.msdn.com/kaevans/archive/2008/01/08/modify-message-content-with-wcf.aspx

However, the example generates a new message on the fly instead of modifying the original message(system generated). And someone may still feel confused on how to customize the system generated message(do some modification in part of it). Here is a simple example I’ve written to demonstrate this.

This example use a simple MessageInspector to modify the request message send from WCF client and the custom MessageInspector is registered into WCF processing pipeline via a custom EndpointBehavior.

The basic idea is that we first create MessageBuffer from the original message and then load the message content from MessageBuffer into a MemoryStream for customization. After that, we generate a new Message from the memoryStream.

Here is the code of the messageInspector and endpointBehavior:

namespace SharedLib

{

    public class SimpleEndpointBehavior : IEndpointBehavior

    {

        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

        {}

        public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)

        {

            clientRuntime.MessageInspectors.Add(

                new SimpleMessageInspector()

                );

        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)

        { }

        public void Validate(ServiceEndpoint endpoint)

        {}

    }

    public class SimpleMessageInspector : IClientMessageInspector, IDispatchMessageInspector

    {

        #region IClientMessageInspector Members

        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)

        {}

        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)

        {

            Console.WriteLine("====SimpleMessageInspector+BeforeSendRequest is called=====");

          

            //modify the request send from client(only customize message body)

            request = TransformMessage2(request);

            //you can modify the entire message via following function

            //request = TransformMessage(request);

            return null;

        }

        //helper method

        //reformat the entire message

        private Message TransformMessage(Message oldMessage)

        {

            Message newMessage = null;

            MessageBuffer msgbuf = oldMessage.CreateBufferedCopy(int.MaxValue);

            XPathNavigator nav = msgbuf.CreateNavigator();

           

  //load the old message into xmldocument

            MemoryStream ms = new MemoryStream();

            XmlWriter xw = XmlWriter.Create(ms);

            nav.WriteSubtree(xw);

            xw.Flush();

            xw.Close();

            ms.Position = 0;

            XDocument xdoc = XDocument.Load(

                XmlReader.Create(ms)

                );

            //perform transformation

            var strElms = xdoc.Descendants(XName.Get("StringValue", "urn:test:datacontracts"));

            foreach (XElement strElm in strElms) strElm.Value = "[Modified in SimpleMessageInspector]" + strElm.Value;

           

            xw = XmlWriter.Create(ms);

            ms.Position= 0;

            xdoc.Save(

     xw

                );

            xw.Flush();

            xw.Close();

            ms.Position = 0;

            StreamReader sr = new StreamReader(ms);

            Console.WriteLine(sr.ReadToEnd());

  

            //create the new message

    ms.Position = 0;

            XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(

                ms, new XmlDictionaryReaderQuotas()

                );

            newMessage = Message.CreateMessage(xdr, int.MaxValue, oldMessage.Version );

            return newMessage;

        }

        //only read and modify the Message Body part

        private Message TransformMessage2(Message oldMessage)

        {

            Message newMessage = null;

            //load the old message into XML

            MessageBuffer msgbuf = oldMessage.CreateBufferedCopy(int.MaxValue);

            Message tmpMessage = msgbuf.CreateMessage();

            XmlDictionaryReader xdr = tmpMessage.GetReaderAtBodyContents();

         XmlDocument xdoc = new XmlDocument();

            xdoc.Load(xdr);

            xdr.Close();

           

            //transform the xmldocument

            XmlNamespaceManager nsmgr = new XmlNamespaceManager(xdoc.NameTable);

            nsmgr.AddNamespace("a", "urn:test:datacontracts");

            XmlNode node = xdoc.SelectSingleNode("//a:StringValue", nsmgr);

            if(node!= null) node.InnerText = "[Modified in SimpleMessageInspector]" + node.InnerText;

            MemoryStream ms = new MemoryStream();

            XmlWriter xw = XmlWriter.Create(ms);

            xdoc.Save(xw);

            xw.Flush();

            xw.Close();

            ms.Position = 0;

            XmlReader xr = XmlReader.Create(ms);

           

            //create new message from modified XML document

            newMessage = Message.CreateMessage(oldMessage.Version, null,xr );

            newMessage.Headers.CopyHeadersFrom(oldMessage);

            newMessage.Properties.CopyProperties(oldMessage.Properties);

       return newMessage;

        }

        #endregion

        #region IDispatchMessageInspector Members

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)

        {

            return null;

        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)

        {}

        #endregion

    }

}

The above message modification code locate a String parameter(member of a composite type) in the WCF message and change its value. The CompositeType’s definition is as below:

namespace SharedLib

{

    // Use a data contract as illustrated in the sample below to add composite types to service operations

    [DataContract(Namespace="urn:test:datacontracts")]

    public class CompositeType

    {

        bool boolValue = true;

        string stringValue = "Hello ";

        [DataMember]

        public bool BoolValue

        {

            get { return boolValue; }

            set { boolValue = value; }

        }

        [DataMember]

        public string StringValue

        {

            get { return stringValue; }

            set { stringValue = value; }

        }

    }

}

And for the client, it’s quite straightforward to inject our custom endpointBehavior(which help inject the messageInspector).

static void CallService()

        {

            using (STSVC.TestServiceClient client = new ClientApp.STSVC.TestServiceClient())

            {

              //inject my endpoint behavior

         client.Endpoint.Behaviors.Add(

                    new SimpleEndpointBehavior()

                    );

                CompositeType obj = new CompositeType() { BoolValue = true, StringValue = "input string" };

                CompositeType repObj = client.GetDataUsingDataContract(obj);

            }

        }

You can get the entire test solution in the attachment.

 

MessageModifierSln.zip