Home / Programming / Blog article: Capture XML In WCF Service

| RSS

Capture XML In WCF Service

April 1st, 2009 | 11 Comments | Posted in Programming

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

Leave a Reply 17795 views, 2 so far today |
Tags: ,
Follow Discussion

11 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

Leave a Reply





Switch to our mobile site