Capture XML In WCF Service
Working on a project where we wrote WCF Services a need was identified to capture the raw xml passed in to the service operation and also capture the reply xml sent back by the service. WCF does not provide such facility out of the box but it can be easily implemented using behaviours. In this article we will look at how to capture raw xml messages when a call is made to a service.
As an example we will create a simple service which implements one operation called SayHello. Our service contract will look like this.
[ServiceContract] public interface IHelloService { [OperationContract] string SayHello(string name); }
In the operation “SayHello” we will return a string. Here is the implementation of IHelloService contract.
public class HelloService : IHelloService { #region IHelloService Members public string SayHello(string name) { return string.Concat("Hello ", name); } #endregion }
We will host our Service In a Windows Forms application so that we can easily view xml we capture. Just to get a feel for it, our host application will look like this. Clicking the button will start the service and as calls are made we will see request and response content in the tabs.
Capturing XML
To capture XML we need to implement two interfaces IDispatchMessageInspector and IServiceBehavior. The way our solution works is that we will add our implementation of IDispatchMessageInspector to MessageInspectors collection on endpoints used by our ServiceHost.
We will first implement IDispatchMessaageInspector in a class called Inspector.
public class Inspector : IDispatchMessageInspector { /// <summary> /// Stores contents of Request message /// passed to the service. /// </summary> /// <value>The request XML.</value> public string Request { get; set; } /// <summary> /// Stores contents of Response messge /// which is sent back to the client. /// </summary> /// <value>The response XmL.</value> public string Response { get; set; } #region IDispatchMessageInspector Members /// <summary> /// Called after an inbound message has been received but /// before the message is dispatched to the intended operation. /// </summary> /// <param name="request">The request message.</param> /// <param name="channel">The incoming channel.</param> /// <param name="instanceContext">The current service instance.</param> /// <returns> /// The object used to correlate state. /// </returns> public object AfterReceiveRequest(
ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel,
System.ServiceModel.InstanceContext instanceContext) { Request = request.ToString(); return null; } /// <summary> /// Called after the operation has returned but before the reply message is sent. /// </summary> /// <param name="reply">The reply message.
/// This value is null if the operation is one way.</param>
/// <param name="correlationState">The correlation object returned from the /// AfterReceiveRequest method.</param> public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply,
object correlationState) { Response = reply.ToString(); } #endregion }
Next we need to create a custom behaviour by implementing IServiceBehavior. The only method we are interested in this interface is the ApplyDispatchBehavior method. Here is our implementation of IServiceBehavior.
public class CustomBehavior : IServiceBehavior { #region IServiceBehavior Members /// <summary> /// Provides the ability to pass custom data to binding elements /// to support the contract implementation. /// </summary> /// <param name="serviceDescription">The service description of the service.</param> /// <param name="serviceHostBase">The host of the service.</param> /// <param name="endpoints">The service endpoints.</param> /// <param name="bindingParameters">Custom objects to which binding elements have access. /// </param> public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { return; } /// <summary> /// Provides the ability to change run-time property values or /// insert custom extension objects such as error handlers, /// message or parameter interceptors, /// security extensions, and other custom extension objects. /// </summary> /// <param name="serviceDescription">The service description.</param> /// <param name="serviceHostBase">The host that is currently being built.</param> public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) { foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers) { dispatcher.Endpoints .ToList() .ForEach(x => x.DispatchRuntime.MessageInspectors.Add(new Inspector())); } } /// <summary> /// Provides the ability to inspect the service host and /// the service description to confirm that the service /// can run successfully. /// </summary> /// <param name="serviceDescription">The service description.</param> /// <param name="serviceHostBase">The service host that is currently being constructed. /// </param> public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) { return; } #endregion }
As said earlier, we will host our service in a Windows Forms application and we would like to display messages in the UI. For this we need access to messages outside the Inspector class we created above. This can be done by raising events when messages are captured. To do just that we will extend our Inspector class with two events and raise them from AfterReceiveRequest and BeforeSendReply methods.
/// <summary> /// Triggered from AfterReceiveRequest method. /// </summary> public event EventHandler<InspectorEventArgs> RaiseRequestReceived; /// <summary> /// Triggered from BeforeSendReply method. /// </summary> public event EventHandler<InspectorEventArgs> RaiseSendingReply; protected void OnRaiseRequestReceived(string message) { EventHandler<InspectorEventArgs> handler = RaiseRequestReceived; if (handler != null) { handler(this, new InspectorEventArgs(message)); } } protected void OnRaiseSendingReply(string message) { EventHandler<InspectorEventArgs> handler = RaiseSendingReply; if (handler != null) { handler(this, new InspectorEventArgs(message)); } }
And we will also modify AfterReceiveRequest and BeforeSendReply to raise these events.
/// <summary> /// Called after an inbound message has been received but /// before the message is dispatched to the intended operation. /// /// This method will also raise RaiseRequestReceived event. /// </summary> /// <param name="request">The request message.</param> /// <param name="channel">The incoming channel.</param> /// <param name="instanceContext">The current service instance.</param> /// <returns> /// The object used to correlate state. /// </returns> public object AfterReceiveRequest( ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext) { Request = request.ToString(); OnRaiseRequestReceived(Request); return null; } /// <summary> /// Called after the operation has returned but before the reply message is sent. /// /// This method will also raise RaiseSendReply event. /// </summary> /// <param name="reply">The reply message. /// This value is null if the operation is one way.</param> /// <param name="correlationState">The correlation object returned from the /// AfterReceiveRequest method.</param> public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState) { Response = reply.ToString(); OnRaiseSendingReply(Response); }
InspectorEventArgs is a simple class used to store and pass information with our events.
public class InspectorEventArgs : EventArgs { public InspectorEventArgs(string message) { this.Message = message; } public string Message { get; set; } }
Hosting The Service And Using Inspector
Now we are at the point where we can use our behaviour to capture request and response messages. First of all we need to put in some code to host our service. This code will go in the click event handler for our button. There are two main things do here. First we add our custom behaviour to the Behaviors collection of our host and second we hook up the events that are raised by our Inspector class.
private void buttonStartService_Click(object sender, EventArgs e) { // Add our Custom Behaviour to the list of behaviours host.Description.Behaviors.Add(behavior); // start the service host.Open(); // add event handlers foreach (ChannelDispatcher dispatcher in host.ChannelDispatchers) { foreach (var endPoint in dispatcher.Endpoints) { // get a list of MessageInspectors that are of type Inspector var query = (from ex in endPoint.DispatchRuntime.MessageInspectors where ex.GetType() == typeof(Inspector) select ex).Cast<Inspector>(); // hook up the events foreach (var item in query) { item.RaiseRequestReceived += new EventHandler<InspectorEventArgs>(Form1_RaiseRequestReceived); item.RaiseSendingReply += new EventHandler<InspectorEventArgs>(Form1_RaiseSendingReply); } } } }
Now when HelloService is called we will see request and response xml in our Windows Forms application which is also a host for HelloService. We will use WebBrowser controls to display XML. Displaying XML in the WebBrowser control is not just setting a property. There is little but not too much work involved. I picked up a nice technique from this link.
/// <summary> /// Writes xml to browser. /// WebBrowser control does render xml properly and to get around /// I am using this handy tip from this link. /// http://www.c-sharpcorner.com/Forums/ShowMessages.aspx?ThreadID=51473 /// </summary> /// <param name="browser">The browser.</param> /// <param name="message">The message.</param> private void WriteToBrowser(WebBrowser browser, string message) { XslCompiledTransform xTrans = new XslCompiledTransform(); xTrans.Load("default.xslt"); StringReader sr = new StringReader(message); XmlReader xReader = XmlReader.Create(sr); System.IO.MemoryStream ms = new MemoryStream(); xTrans.Transform(xReader, null, ms); ms.Position = 0; browser.DocumentStream = ms; }
Creating The Client
Client for HelloService is a simple Windows Forms application with a text box, button and a label. You can download the code at the end of this article and see it for yourself.
The Output
Here are screenshots of both client and host showing request and response XML.
I hope you found this article interesting. If yes, then how about subscribing to the feed :)
21 Responses to Capture XML In WCF Service
Leave a Reply Cancel reply
Top Posts
- LINQ To SQL Tutorial
- LINQ To SQL Join On Multiple Conditions
- Code Sample: Programmatically Download File Using C#
- Windows 7 Control Panel In Classic Mode
- More Details Emerge On Microsoft Master Certification
- Use SqlConnection With LINQ To SQL
- Free Icons And Images With Visual Studio 2008
- Capture XML In WCF Service
- Dynamic Sort With LINQ
- StyleCop Tutorial
Tags
.Net 2010 ADO.NET ASP.NET Azure Blogging Books Browsers C# Certification Cloud Computing Code Snippets Community Data Services Eclipse Entity Framework Google IDE Java LINQ Mac Microsoft Museum NetBeans Office Oracle REST SharePoint Silverlight SQL Server T-SQL Tips Tools Training Visual Studio Visual Studio 2010 WCF Web Windows Windows 7 Windows Forms Windows Live WMI WPF XAML


This is great stuff. It’s exactly what I was looking for, and very well explained. Got it to work in my own project in no time. Thanks a lot!
Thanks Miel. It is always nice to know that my post was helpful.
When i execute your provided code, it gives me an error.
There was no endpoint listening at http://localhost:8731/HelloService/ that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details.
Hi Sanjeev,
Most likely the service is not running at that address. Can you run the service through visual studio and then put the URL in your browser. Verify that you can actually browse the service from IE.
Let me know if you face any issues.
i ran your project.. Service is in your attachment rt?
What happens when you hit that endpoint in your browser. Do you see anything?
When i hit ” http://localhost:8731/HelloService/” in browser it shows bad server error.
Hi Sanjeev,
To me it sounds like the service is not running. Can you confirm if you are running the service? You can do that by making the service project as a startup project and then hit Ctrl + F5.
In service project it shows baseaddress = “http://localhost:8731/Design_Time_Addresses/CaptureXML.Service/Service1/”.. and its running and in client it shows address as mentioned in above message.
So that is the endpoint for the service, but you are trying to access the service at a different address as you mentioned in your comments. Try to access the service on an address where it is running. That should work.
BTW: What’s your objective?
Have you checked out the WCF tracing capabilities? They do exactly what you replicated – basically logging all incoming and outgoing requests, full body with all the SOAP headers and everything, and tracing can store these things in XML files or send them to a output window or whatever you wish to do.
Check it out! Search for “WCF tracing” and you should find tons of hits
While the WCF IDispatchMessageInspector does give you access to the messages xml request it does not give you access to the true raw message if there is an issue with it.
{… Error reading body: System.Xml.XmlException: Start element ‘plan:req’ does not match end element ‘plan:Echo’. Line 15, position 18. …} System.ServiceModel.Channels.Message {System.ServiceModel.Channels.BufferedMessage}
WCF should give you a way to get to the raw message data prior to the xml deserialzation.
The value of true message logging is you can log messages that are good and messages that are bad, and IDispatchMessageInspector only allows good, also I would not expect a non-development box to use WCF Tracing, nor would that type of tool be allowed in a production install of a product.
This is a great example! Can you tell me if it is tied to a SOAP-based implementation of WCF? I am trying to take your principals here and merge them into a REST-based implementation I have, and am always getting a NULL message (In InspectorEventArgs).
Any thoughts on how we might do this in a REST-based WCF Service?
Is there a different class that I must use to account for this when I configure the Behavior?
I have a Win32 Service that we host our REST-based WCF service in.
Hi,
I got the web services working but there is no request and response date showing. Look like the events don’t get fired. Can you please advice on this?
Cheers,
Hi Brandon,
You have hooked the events, right?
item.RaiseRequestReceived +=(Form1_RaiseRequestReceived); (Form1_RaiseSendingReply);
new EventHandler
item.RaiseSendingReply +=
new EventHandler
It should work as I have been using this code in many projects. I did modify it slightly for some work I did recently but it was only because the host is not a Windows Forms app.
I can have a look at your code if you like.
Regards,
Deepak
Hi Deepak,
First of all thanks for the great code,am trying to write a WCF to grab xml message from MSMQ and route to another queue.
Just downloaded your code and convert the version to VS2010 but when try to build i got these below errors,can you pls advise me what am missing here ?
Error 1 The type or namespace name ‘Client’ does not exist in the namespace ‘CaptureXML.Client.CaptureXML’ (are you missing an assembly reference?) C:\CRS_PROJECT\CaptureXML\CaptureXML.Client\Service References\CaptureXML.Service\Reference.cs 23 56 CaptureXML.Client
Shan,
I regenerated the proxy and it works fine now. I suggest that you do the same.
Very helpful piece of code. No one else has attempted anything similar. Thanks for that. What I am looking for is slightly different. I am consuming an external service. I have no control over this third party service. I need to capture data sent and received. (Something completely opposite to what you have implemented.) :-)
My requirement is to log SOAP request and SAOP response in the WCF service inside Service method call itself.
Can you please help me on this?
I’m looking for something that could sniff over the wire.
Do you have any idea for it? Thanks
I have a problem with the Form1 under captureXML.client
cannot find namespace
CaptureXML.Service client = new CaptureXML.Service();