How to log the raw request in WCF service

Maloric picture Maloric · Apr 20, 2012 · Viewed 26.8k times · Source

I have a WCF service with several methods. I would like to log the raw request that came in from the client regardless of how this was sent. One method accepts the data as a querystring (strictly for legacy support) which I can log using:

OperationContext.Current.IncomingMessageHeaders.To.AbsoluteUri

That is sufficient in that scenario, but other methods allow the client to send data as XML using a proxy class generated by svcutil.exe. In this scenario I have found the data I want in the s:Body of:

OperationContext.Current.RequestContext.RequestMessage

Unfortunately, no matter what I try I can't create a buffered copy of the message before it is read. Here is an example:

public CascadeResponse SendCustomer(Customer c)
    {
        Message msg = OperationContext.Current.RequestContext.RequestMessage.CreateBufferedCopy(Int32.MaxValue).CreateMessage();
        LogMessage(msg);
        // Now that the request is logged, get on with the rest
    }

On the first line of SendCustomer, however, I get the following error:

This message cannot support the operation because it has been read.

This is the point of me creating the buffered copy, surely? I'm guessing that I'm doing something elementally wrong here.

Edit:

Ok, so the method is now like this:

public CascadeResponse SendCustomer(Message requestMessage)
    {
        Message msg = OperationContext.Current.RequestContext.RequestMessage.CreateBufferedCopy(Int32.MaxValue).CreateMessage();
        LogMessage(msg);
        // Now that the request is logged, get on with the rest        
        Customer c = msg.GetBody<Customer>();
        string clientKey = "1111"; // This should be the clientKey string passed to the function along with the customer 
        return SendLead(c, clientKey);
    }

My problem is that I don't know how to get the Customer and ClientKey sent as separate entities. I could make clientKey a property of Customer (or create a custom object that is specifically for passing data in and contains Customer and ClientKey as attributes), but I would like to avoid that if possible, as this is an upgrade of a legacy system that already works this way.

I am also having trouble using svcUtil.exe to create my proxy classes - I assume that having the above method signature means my service will no longer advertise the correct signature to send requests as? Not sure if that is clear enough - if my only input method accepts a Message object, how does my client know to send a Customer and a ClientKey?

Answer

Maloric picture Maloric · Apr 23, 2012

I found a solution that others might also find useful. Creating a MessageInspector allows you to attach code to the "AfterReceiveRequest" and "BeforeSendReply" events, as per the following:

public class MessageInspector : IDispatchMessageInspector
{
    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
        request = buffer.CreateMessage();
        LogMessage("Received:\n{0}", buffer.CreateMessage().ToString());
        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
        reply = buffer.CreateMessage();
        LogMessage("Sending:\n{0}", buffer.CreateMessage().ToString());
    }
}

There is a full tutorial for setting up message inspectors fo wcf here. I will say be careful to check your fully qualified assembly name when you are adding the behavior extension to your app.config/web.config.

Hope someone else finds this useful.