[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.
Comments
Anonymous
February 20, 2009
PingBack from http://www.clickandsolve.com/?p=12522Anonymous
February 21, 2009
Where is the web reference "http://localhost:11111/SimpleTestService/?wsdl"Anonymous
February 21, 2009
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.Anonymous
August 01, 2009
Smashing! Great work, a lot clearer than the Ms examplesAnonymous
August 02, 2009
The comment has been removedAnonymous
November 02, 2009
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">", the regenerated message is missing the signature Id and only shows <Body> in the new message. Do you have solution for it? Thanks, MaxAnonymous
January 24, 2014
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!!Anonymous
May 10, 2015
The oldMessage is not released any where, is this make memory leak error?Anonymous
December 08, 2015
The comment has been removed