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.

image 

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.

WCF Request

WCF Response

WCF Client

I hope you found this article interesting. If yes, then how about subscribing to the feed :)

Download Code

Tagged with:
 

21 Responses to Capture XML In WCF Service

  1. Miel Bronneberg says:

    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!

  2. Deepak says:

    Thanks Miel. It is always nice to know that my post was helpful.

  3. Sanjeev says:

    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.

  4. Deepak says:

    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.

  5. Sanjeev says:

    i ran your project.. Service is in your attachment rt?

  6. Deepak says:

    What happens when you hit that endpoint in your browser. Do you see anything?

  7. Sanjeev says:

    When i hit ” http://localhost:8731/HelloService/” in browser it shows bad server error.

  8. Deepak says:

    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.

  9. Sanjeev says:

    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.

  10. Deepak says:

    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?

  11. Marc Scheuner says:

    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

  12. AC says:

    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.

  13. 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.

  14. 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,

  15. Deepak says:

    Hi Brandon,

    You have hooked the events, right?

    item.RaiseRequestReceived +=
    new EventHandler(Form1_RaiseRequestReceived);
    item.RaiseSendingReply +=
    new EventHandler
    (Form1_RaiseSendingReply);

    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

  16. Shan says:

    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

  17. Deepak says:

    Shan,

    I regenerated the proxy and it works fine now. I suggest that you do the same.

  18. dotNet says:

    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.) :-)

  19. Viji says:

    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?

  20. hakkai says:

    I’m looking for something that could sniff over the wire.
    Do you have any idea for it? Thanks

  21. elaine says:

    I have a problem with the Form1 under captureXML.client

    cannot find namespace

    CaptureXML.Service client = new CaptureXML.Service();

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>