Tuesday, February 24, 2009

Inspecting Messages with an IClientMessageInspector

I was building up a library which consumes a RESTful API, and needed some way of intercepting the incoming messages before serialisation so I could inject some custom logic. The API is facebooks one in particular which isn't really truly RESTful but that's another story.

Effectively some of the service methods can either return the expected return message if nothing has gone wrong, or an exception POX message if something has gone wrong. This could range from a mal-formed querystring exception to permissions or an incorrect method call. So I wanted some way to catch this and throw a proper .NET exception instead of WCF throwing a could not deserialise exception when it's unable to hydrate an object designed to match this message:

<?xml version="1.0" encoding="UTF-8"?>
<fql_query_response 
  xmlns="http//api.facebook.com/1.0/" 
  xmlnsxsi="http//www.w3.org/2001/XMLSchema-instance" 
  xsischemaLocation="http//api.facebook.com/1.0/ http//api.facebook.com/1.0/facebook.xsd"
  list="true">
  <user>
    <name>Mark Zuckerberg</name>
  </user>
  <user>
    <name>Chris Hughes</name>
  </user>
</fql_query_response>


with this message:

<?xml version="1.0" encoding="UTF-8"?>
<error_response
  xmlns="http://api.facebook.com/1.0/"
  xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation ="http://api.facebook.com/1.0/ http://api.facebook.com/1.0/facebook.xsd">
  <error_code>5</error_code>
  <error_msg>Unauthorized source IP address (ip was 10.1.2.3)</error_msg>
</error_response>


So I really wanted to hook into the point at which WCF does the deserialising, catch the exception, and then rethrow my custom facebook exception. However I wasn't able to pull that off (lack of documentation) so for now I'm simply inspecting each message individually. This is not the best solution but will do until I figure out how to do it more gracefully and efficiently. It serves as an example for now.

The first thing we need to do is add a new behaviour to our Endpoint which will subsequently add our new message inspection logic.


var customServiceProxy = new ChannelFactory<ICustomService>("CustomService");
customServiceProxy.Endpoint.Behaviors.Add(new HookServiceBehaviour());


The next step is to add our new message inspector.


public class HookServiceBehaviour  IEndpointBehavior     
{         
    #region Implementation of IEndpointBehavior               
    
    public void Validate(ServiceEndpoint endpoint){}         
   
    public void AddBindingParameters(ServiceEndpoint endpoint, 
        BindingParameterCollection bindingParameters){}         
    
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, 
        EndpointDispatcher endpointDispatcher){}                  
    
    public void ApplyClientBehavior(ServiceEndpoint endpoint,                
        ClientRuntime clientRuntime)        
    {            
        clientRuntime.MessageInspectors.Add(new CustomMessageInspector());        
    }     
        
    #endregion  
}


And implement our custom message inspector:


public class CustomMessageInspector  IClientMessageInspector    
{         
    #region Implementation of IClientMessageInspector              
    public object BeforeSendRequest(ref Message request,             
        IClientChannel channel)         
    {             
        return request;         
    }              
    public void AfterReceiveReply(ref Message reply, object correlationState)        
    {            
        MessageBuffer messageBuffer = reply.CreateBufferedCopy(int.MaxValue);            
        Message message = messageBuffer.CreateMessage();                 
        XmlDictionaryReader contents = message.GetReaderAtBodyContents();                        
        
        //inspect contents and do what you want, throw a custom              
        //exception for example...                 
        //We need to recreate the reply to resume the deserialisation as it             
        //can only be read once.            
        reply = messageBuffer.CreateMessage();            
        messageBuffer.Close();        
    }             
    #endregion    
}


An actual implementation of this example can be found here: http://www.codeplex.com/fluentfacebook

No comments:

Post a Comment