Message Inspectors can be a very usefull tool in diagnosing problems between WCF services and clients.
The messages that are transferred between clients/services can be intercepted and operations performed on them.
We’ve used this at work in conjunction with a tool called SaopUI to capture the SOAP messages and fire them at our service.
This can be usefull for load testing, concurrency testing scenarios amongst others.
What I’ll be demonstrating
Sending a message from our client to the service and receiving a reply.
Capturing the SOAP packets in their raw form on the service, before they are processed by the service operations.
The parts we’ll be going over from my example solution
A service (CoffeeMakingservice)
A client (CoffeeAddict)
The message inspecting components
Service implementation
using System.Threading; namespace CoffeeAddiction { public class CoffeeShop : ICoffeeShop { public Coffee MakeCoffee(CoffeeType coffeeType) { return RunBarrister(coffeeType); } private Coffee RunBarrister(CoffeeType coffeeType) { Thread.Sleep(5000); return new Coffee() { TypeOfCoffee = coffeeType }; } } }
Client
using System; using CoffeeAddict.ServieProxy; namespace CoffeeAddict { class Program { static void Main(string[] args) { Console.WriteLine("Hi there. What type of Coffee would you like today?"); ConsoleKey cK; do { DisplayOptionsToAddict(); ProcessOrder(); Console.WriteLine("Hit the Escape key to exit the shop. Hit any other key to order more joe."); Console.WriteLine(); cK = Console.ReadKey(true).Key; } while (cK != ConsoleKey.Escape); } private static void ProcessOrder() { Coffee yourCoffee; char selectedKey; int option; bool selectionRequested = false; do { if (selectionRequested) Console.WriteLine("You have entered an invalid option. Please try again."); selectedKey = Console.ReadKey(true).KeyChar; int.TryParse(selectedKey.ToString(), out option); if (!selectionRequested) selectionRequested = true; } while (option < 1 || option > Enum.GetValues(typeof(CoffeeType)).Length); Console.WriteLine("Thank you. We will now process your order."); using (CoffeeShopClient coffeeShopClient = new CoffeeShopClient()) yourCoffee = coffeeShopClient.MakeCoffee((CoffeeType)option); Console.WriteLine(); Console.WriteLine("Your {0} is served!", yourCoffee.TypeOfCoffee); } private static void DisplayOptionsToAddict() { Console.WriteLine("Please select the associated number"); Console.WriteLine(); foreach (string coffeeType in Enum.GetNames(typeof(CoffeeType))) { Console.WriteLine("{0} : {1}", Convert.ToInt32(Enum.Parse(typeof(CoffeeType), coffeeType)), coffeeType ); } Console.WriteLine(); } } }
I’ve hosted these projects in console apps for simplicities sake.
Run the service.
Now generate our ServiceProxy.
You would have to do this before you write the client.
Now we’re ready to start the client.
The client will give a welcome and display a list of options to choose from.
Once the user has made his/her choice, a proxy of the service is created and the users choice is sent to the service for coffee production to begin.
Once the coffee is made, it’s returned to the client.
The client can then decide whether he/she wants another coffee.
——
Now for the message inspection part
As we are going to be inspecting the messages on the service side, we implement the IDispatchMessageInspector.
If we were inspecting the messages on the client side, we would implement the IClientMessageInspector.
As you can see, the methods on these interfaces take a Message parameter.
The Message contains the serialized parameters, operation name, and other information about the message being inspected.
The body of the message is implemented as a streamed object, so we’ve given it some special attention.
Also the body of the message can be processed only once during the lifetime of the Message object.
Any further attempts to retrieve the body, will result in an InvalidOperationException.
The solution to this is to create a copy, by using the CreateBufferedCopy method of the message.
This allows us to create a copy of the original message by using the CreateMessage method on the new MessageBuffer we now have.
As you can see, we write the message out to a time stamped file.
using System; using System.IO; using System.ServiceModel; using System.ServiceModel.Dispatcher; using System.ServiceModel.Channels; using System.Xml; namespace MessageListener.Instrumentation { public class MessageInspector : IDispatchMessageInspector { const string LogDir = @"C:\Logs\CoffeeMakingService\"; private Message TraceMessage(MessageBuffer buffer) { //Must use a buffer rather than the origonal message, because the Message's body can be processed only once. Message msg = buffer.CreateMessage(); //Setup StringWriter to use as input for our StreamWriter //This is needed in order to capture the body of the message, because the body is streamed. StringWriter stringWriter = new StringWriter(); XmlTextWriter xmlTextWriter = new XmlTextWriter(stringWriter); msg.WriteMessage(xmlTextWriter); xmlTextWriter.Flush(); xmlTextWriter.Close(); //Setup filename to write to if (!Directory.Exists(LogDir)) Directory.CreateDirectory(LogDir); DateTime now = DateTime.Now; string datePart = now.Year.ToString() + '-' + now.Month.ToString() + '-' + now.Day.ToString() + '-' + now.Hour + '-' + now.Minute + '-' + now.Second; string fileName = LogDir + "\\" + datePart + '-' + "SoapEnv.xml"; //Write to file using (StreamWriter sw = new StreamWriter(fileName)) sw.Write(stringWriter.ToString()); //Return copy of origonal message with unalterd State return buffer.CreateMessage(); } //BeforeSendReply is called after the response has been constructed by the service operation public void BeforeSendReply(ref Message reply, object correlationState) { //Uncomment the below line if you need to capture the reply message //reply = TraceMessage(reply.CreateBufferedCopy(int.MaxValue)); } //The AfterReceiveRequest method is fired after the message has been received but prior to invoking the service operation public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { request = TraceMessage(request.CreateBufferedCopy(int.MaxValue)); return null; } } }
Now we have to add the message inspector to the services channel stack.
First we create the behavior.
Now the only methods we are likely to want to use are the ApplyClientBehavior and/or ApplyDispatchBehavior.
ApplyDispatchBehavior provides the customization we will need to add our new inspector to the collection of message inspectors on the dispatch runtime of our endpoint dispatcher on the service side.
IEndpointBehavior implementation
using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher; namespace MessageListener.Instrumentation { public class LoggingEndpointBehavior : IEndpointBehavior { public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) {} public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) {} public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { MessageInspector inspector = new MessageInspector(); endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector); } public void Validate(ServiceEndpoint endpoint) {} } }
Now we need to add our behavior to a behavior extension.
The BehaviorType property and the CreateBehavior method must be overridden.
By doing this we provide an instance of our behavior when the framework sees that we have added directions to our extension in the configuration.
Sub classing BehaviorExtensionElement
using System; using System.ServiceModel.Configuration; namespace MessageListener.Instrumentation { public class LoggingBehaviorExtensionElement : BehaviorExtensionElement { public override Type BehaviorType { get { return typeof(LoggingEndpointBehavior); } } protected override object CreateBehavior() { return new LoggingEndpointBehavior(); } } }
After the behavior is added to an extension, it can be added to the channel stack.
We tell the framework where to look for our extension, by adding directions to our configuration file.
We now add a reference to our extension (messageInspector) to the endpoint behavior.
Important parts of the services config file.
I’ve left some parts out of this example, to allow us to focus on the listening functionality.
<system.serviceModel>
Now when we run the service and the client, we can enjoy the fruits of our labor.
Click on link below to download full source code.
You can grap the MessageListenerDemo code here.
You’ll need Visual Studio 2010 unless you want to create new projects using the source files.
public class WSDualHttpBinding : Binding,... { public Uri ClientBaseAddress {get;set;} //More members }
December 15, 2010 at 08:31 |
Hello!!! Excellent article you’ve saved my day, I didn’t even know if this was possible
December 15, 2010 at 23:46 |
Great to be able to help!
March 14, 2012 at 06:03 |
Hello,
The sample file is deleted.. can you please provide me the link where I can download?
March 14, 2012 at 22:56 |
Thanks for the heads up.
I’ve raised a ticket to media fire questioning what they have done with it.
July 18, 2012 at 12:37 |
Hello binarymist, thanks for the great article … would you be so kind to re-upload full source code in case you still have it ?
July 19, 2012 at 00:42 |
Your in luck. Check link above.
July 19, 2012 at 09:50 |
thanks
September 5, 2012 at 00:01 |
Great!!! Thank tou!!!
June 19, 2014 at 12:52 |
Hello thanks for th article but i tried and when i compile it. The vs2010 thorws several errors telling me that i dont have all the needed references.
June 19, 2014 at 12:57 |
Hello i just added system.xml.serialization and system.configuration … and wonder compile it weelll… thanks
April 4, 2017 at 07:40 |
Great article – what are the config file updates (seem to be masked in article)
October 7, 2017 at 04:21 |
Thank you. Wish we could see the code or the config file to fully understand what is going on.