|
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 :)
Get Updates By Email
Popular Post
- LINQ To SQL Tutorial
- LINQ To SQL Join On Multiple Conditions
- Code Sample: Programmatically Download File Using C#
- Free Icons And Images With Visual Studio 2008
- Windows 7 Control Panel In Classic Mode
- Dynamic Sort With LINQ
- Use SqlConnection With LINQ To SQL
- StyleCop Tutorial
- Write To Vista Event Log Using C#
- More Details Emerge On Microsoft Master Certification
Tag Cloud
Code Snippets
- Get Current Windows User In C#
- Get Width And Height Of Image In C#
- Get Windows Registry Size With WMI And C#
- Reverse Array Elements Using C#
- Convert Hexadecimal To Number In C#
- Get Free Disk Space Using T-SQL
- SQL Server 2008 – Get All Indexes In A Database
- Get Name Of Current Executing Assembly In C#
- Get CD Or DVD Drive Information Using WMI And C#
- Get Last Row From Table Using LINQ To SQL


June 9th, 2009 at 12:27 am
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!
June 9th, 2009 at 10:35 am
Thanks Miel. It is always nice to know that my post was helpful.
August 24th, 2009 at 9:20 pm
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.
August 25th, 2009 at 10:52 am
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.
August 25th, 2009 at 4:04 pm
i ran your project.. Service is in your attachment rt?
August 25th, 2009 at 4:06 pm
What happens when you hit that endpoint in your browser. Do you see anything?
August 25th, 2009 at 4:50 pm
When i hit ” http://localhost:8731/HelloService/” in browser it shows bad server error.
August 25th, 2009 at 4:53 pm
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.
August 25th, 2009 at 5:05 pm
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.
August 25th, 2009 at 5:08 pm
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?
October 5th, 2009 at 6:41 am
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