Over the past couple of weeks I’ve been doing a lot of work with WCF to do with the messages being sent between client and server and securely signing them. Signing the message isn’t the subject of this post however developing the following technique to see the message before it’s sent did lead me onto the solution for debugging messages being sent between client and server.
This post is to show how you can intercept the message, update it once its been serialized into xml (or just look at the xml) but before it’s been sent to the server. The basis of this all down the the IClientMessageInspector and adding this to your client calling code.
Let’s take a look at the message inspector and then I’ll talk through the additional classes which go with the inspector and then finally I’ll show you how to use the message inspector with a WCF client proxy generated class; in both code and through configuration.
IClientMessageInspector
public class DebugMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
}
Above is the generated method signatures of which the IClientMessageInspector expects to be implemented. The naming of the methods describes exactly the point in which they are called; BeforeSendRequest is as the request is sent to the server, AfterReceiveReply is as the response is returned. On the way out this is executed after the message has been created from the strongly typed objects and on the way in it executes before any serialization is performed if using strongly typed proxy and class definitions generated from a WSDL.
Inside each of the methods you can call the following code to allow inspection of the message.
private Message Intercept(Message message)
{
// read the message into an XmlDocument as then you can work with the contents
// Message is a forward reading class only so once read that's it.
MemoryStream ms = new MemoryStream();
XmlWriter writer = XmlWriter.Create(ms);
message.WriteMessage(writer);
writer.Flush();
ms.Position = 0;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = true;
xmlDoc.Load(ms);
// read the contents of the message here and update as required; eg sign the message
// as the message is forward reading then we need to recreate it before moving on
ms = new MemoryStream();
xmlDoc.Save(ms);
ms.Position = 0;
XmlReader reader = XmlReader.Create(ms);
Message newMessage = Message.CreateMessage(reader, int.MaxValue, message.Version);
newMessage.Properties.CopyProperties(message.Properties);
message = newMessage;
return message;
}
The above is a very simple way of reading the message into a raw xml format and then converting it back into a Message for further processing from the framework.
So how do you hook the message inspector to the client proxy call?
You need an IEndPointBehavior.
IEndpointBehavior definition
The new end point behavior allows you to add, amongst other things, message inspectors to the current ClientRuntime of the client definition using the behavior.
Implementing the interface gives you the following structure:
public class DebugMessageBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
// add the inspector to the client runtime
clientRuntime.MessageInspectors.Add(new DebugMessageInspector());
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
As you can see a number of extension hook points are available, but for this post we’ll concentrate on the “ApplyClientBehavior” method. In this method it gives you access to the MessageInspectors collection on the ClientRuntime which is a SynchronizedCollection on which any IClientMessageInspector implemented class instances can be added to.
This is it for the behavior definition. Lets see how we use it with a WCF client proxy class.
How do we use the IClientMessageInspector?
There are two ways in which the inspector can be used with a client proxy class. It can either be added in via code or through configuration. There are pros and cons for both routes. If done through code it will always be added, however a con is it will always be added. If however it is done through configuration it can be enabled/disabled without recompiling the code. This can be especially handy if the functionality is only for debugging and you don’t want it to be used during Production or it’s used to turn on/off certain functionality which is only applicable under certain circumstances.
Let’s take a look at using the message inspector first with code and then through configuration.
Using the IClientMessageInspector in code
Through code (example code can be found on GitHub) you add it to the Endpoint Behavior collection directly. This can be done either straight after the proxy definition or at any point before any method is actually executed.
var client = new ServiceReference1.Service1Client(); client.Endpoint.Behaviors.Add(new DebugMessageBehavior());
As you can see from the above it is instantiated calling a default constructor. At this point you can pass in constructor parameters however this will cause you issues if you want to move it to be bound in configuration later.
And that’s it. Straight forward; simple.
Using the IClientMessageInspector through configuration
Adding the behavior through configuration takes a bit more work but as stated earlier being able to attach the functionality purely through configuration without changing code is worth the extra effort. To attach through configuration we need to define a new BehaviorExtensionElement which describes our new behavior.
public class DebugMessageBehaviourElement : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
return new DebugMessageBehavior();
}
public override Type BehaviorType
{
get
{
return typeof(DebugMessageBehavior);
}
}
}
Once this has been created and the solution built the binding configuration needs to updated as below:
<behaviors>
<endpointBehaviors>
<behavior name="debugInspectorBehavior">
<debugInspector/>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="http://localhost:60428/Service1.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IService1" contract="ServiceReference1.IService1"
name="BasicHttpBinding_IService1" behaviorConfiguration="debugInspectorBehavior" />
</client>
<extensions>
<behaviorExtensions>
<add name="debugInspector" type="Client.DebugMessageBehaviourElement, Client"/>
</behaviorExtensions>
</extensions>
This is all in the system.serviceModel node in your web/app config file. The important parts are highlighted in bold.
First off you have to define a behavior extension pointing at your newly created behavior element class. This is a reflection string so the fully qualified type name needs to be followed by the name of the assembly that contains it.
Secondly you create an endpoint behavior which now uses the newly created extension. At this point the intellisense in Visual Studio will complain however it’s fine.
Third and finally you specify the newly created endpoint behavior as the behaviorConfiguration for your client end point.
Result
Either way, on the way out you can see the whole message is:
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IService1/GetData</Action>
</s:Header>
<s:Body>
<GetData xmlns="http://tempuri.org/">
<value>7</value>
</GetData>
</s:Body>
</s:Envelope>
And on the way back:
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetDataResponse xmlns="http://tempuri.org/">
<GetDataResult>You entered: 7</GetDataResult>
</GetDataResponse>
</s:Body>
</s:Envelope>
Conclusion
Message inspectors are a very powerful tool when working with WCF and integration services. They can be used for debugging purposes as shown or for performing other actions on the message before it gets sent or on its return such as securely signing the message with a certificate.
The demo code I’ve used for this post can be found on Github; but remember it comes with a “Works on my machine” seal so use at own risk.