[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


http://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

Comments (9)

  1. Hi Vinh,

    The solution contains "ServiceApp" which is the WCF service, you need to run it first. Then, it will setup the service endpoint "http://localhost:11111/SimpleTestService/" and "http://localhost:11111/SimpleTestService/?wsdl" is the metadata endpoint which you can add serviceReference against.

  2. Bob says:

    Smashing!  Great work, a lot clearer than the Ms examples

  3. James says:

    HI Steven,

    Exactly what I was looking for, but I cannot seem to get this o work with my setup.

    I’ve got a Wcf client, connected to a non wcf soap service. When I plug in the MessageInspector, this breaks the service, and I get the following error:

    System.Xml.XmlException was unhandled by user code

     Message="The data at the root level is invalid. Line 1, position 1."

     Source="mscorlib"

     LineNumber=0

     LinePosition=0

    If I comment out the call to the "TransformMessage" / "TransformMessage2" methods, and step through the code in the "AfterReceiveReply" and "BeforeSendRequest" methods, I can see the SOAP xml and everything works,

    SO I guess there’s an issue with using this method to manipulate a Message that’s non wcf service (I guess).

    When I view the XML contents for the SOAP parcel, I can see everything as normal (for a soap response).

    Howeverm when I call the "TransformMessage", and view the XML contents of the message, I see the following, instead of the soap contents.

    {<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"&gt;

     <s:Header />

     <s:Body>… stream …</s:Body>

    </s:Envelope>}

    COuld it be the "… stream …" thats preventing the soap from being parsed?

    Can anybody help?

  4. Max says:

    Hi Steven,

    This is a great one that can help my current work!

    One issue is when i use TransformMessage to change header, and there is a signature on body, i.e.

    "<Body wsu:Id="wssecurity_signature_id_001" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">&quot;, the regenerated message is missing the signature Id and only shows <Body> in the new message.

    Do you have solution for it?

    Thanks,

    Max

  5. Peps says:

    I know that this post is old, but I used this code in a message change un body SOAP request, I used  TransformMessage2() method and the result is  <s:Body>… stream …</s:Body> .. does anyone knows why??? Any Help on getting the changed body message?

    Thanks!!

  6. Vong says:

    The oldMessage is not released any where, is this make memory leak error?

  7. Kevin Le says:

    I was running into the "The data at the root level is invalid. Line 1, position 1." error too.

    Found out that the MemoryStream object needs to be reset, essentially nulls it out, before saving the new XML:

    ms.SetLength(0);

    xdoc.Save(xw);