As we are building out our mobile WCF endpoints we want to be able to track/trend the size of messages which are being sent to our phones. We want to do this for multiple reasons 1) to trend our message sizes over time to make sure we are being good little developers 2) to be able to report and measure the amount of data we are sending to each of our mobile devices because bandwidth is not free.
I will say that my approach may just be ‘one way’ to accomplish this, but it works for me.
In order to accomplish task we will be extending WCF in multiple places and creating multiple files. I will list out a quick list of the files then dive into each file a bit further. In the end we will have a working solution which will be good enough to get you started.
WCF Extension points
- CustomServiceHost => This extends ServiceHost
- CustomServiceHostFactory => This extends ServiceHostFactory
- MessageDiagnosticsInterceptor => This implements the IDispatchMessageInspector interface (used to grab the outbound message)
- MessageDiagnosticsServiceBehavior => This implements the IServiceBehavior interface (used to setup the interceptor)
The way this will work is this. We will need to create each of our 4 custom classes, but the glue that binds this all together is how we setup our endpoint to use the CustomServiceHostFactory.
Creating our Custom Service Host
public class CustomServiceHost : ServiceHost
{
public CustomServiceHost()
{
}
public CustomServiceHost( Type serviceType, params Uri[] baseAddresses )
: base( serviceType, baseAddresses )
{
}
protected override void OnOpening()
{
Description.Behaviors.Add( new MessageDiagnosticsServiceBehavior() );
base.OnOpening();
}
}
In the above code the only real important thing to notice is that we are adding a behavior during the OnOpening call. We will define this behavior below.
Creating our Custom Service Host Factory
public class CustomServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost( Type serviceType, Uri[] baseAddresses )
{
return new CustomServiceHost( serviceType, baseAddresses );
}
}
In the code above we are implementing our Host Factory and what we are doing here is returning an instance of our custom host we defined above.
Creating our Message Diagnostics Interceptor
public class MessageDiagnosticsInterceptor : IClientMessageInspector, IDispatchMessageInspector
{
public object BeforeSendRequest( ref Message request, IClientChannel channel )
{
return null;
}
public object AfterReceiveRequest( ref Message request, IClientChannel channel, InstanceContext instanceContext )
{
return null;
}
public void AfterReceiveReply( ref Message reply, object correlationState )
{
}
public void BeforeSendReply( ref Message reply, object correlationState )
{
try
{
if ( !reply.IsFault && !reply.IsEmpty )
{
var messageBodyReader = reply.GetReaderAtBodyContents();
var messageBody = messageBodyReader.ReadOuterXml();
DetermineAndLogMessageDiagnostics( messageBody );
RebuildMessage( ref reply, messageBody );
}
}
catch ( Exception e )
{
Debug.WriteLine( e );
}
}
private void DetermineAndLogMessageDiagnostics( string messageBody )
{
double bodySizeInBytes = Encoding.UTF8.GetByteCount( messageBody );
var asKiloBytes = bodySizeInBytes / 1024;
Debug.WriteLine( "Message size in KBytes {0}", asKiloBytes );
}
private static void RebuildMessage( ref Message message, string messageBody )
{
if ( message.IsFault || message.IsEmpty ) return;
var bodyDoc = new XmlDocument();
bodyDoc.LoadXml( messageBody );
// Create new message
var replacementMessage = Message.CreateMessage( message.Version,
null,
new XmlNodeReader( bodyDoc.DocumentElement ) );
replacementMessage.Headers.CopyHeadersFrom( message );
foreach ( var propertyKey in message.Properties.Keys )
{
replacementMessage.Properties.Add( propertyKey, message.Properties[ propertyKey ] );
}
// Close the original message and return new message
message.Close();
message = replacementMessage;
}
}
The above is the ‘hair’ part of all of our logic. It is here that we actually get the message and can do something with it. You will notice that in the method BeforeSendReply we are grabbing the body of the message by using the GetReaderAtBodyContent() method. One thing to note here is that since this is going to return an XmlDictionaryReader you can only call this message ONE TIME. In fact you should also notice that I am pulling out the message body and using this in the RebuildMessage method. This rebuild method is needed because I accessed the XmlDictionaryReader, without this you will get exceptions.
The code where you would want to extend to do your own diagnostics would be in the DetermineAndLogMessageDiagnostics method. Here is where you can do anything you want with the message.
Creating our Message Diagnostics Service Behavior
public class MessageDiagnosticsServiceBehavior : IServiceBehavior
{
public void Validate( ServiceDescription serviceDescription, ServiceHostBase serviceHostBase )
{
}
public void AddBindingParameters( ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection endpoints, BindingParameterCollection bindingParameters )
{
return;
}
public void ApplyDispatchBehavior( ServiceDescription serviceDescription, ServiceHostBase serviceHostBase )
{
foreach ( ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers )
{
foreach ( EndpointDispatcher epDisp in chDisp.Endpoints )
{
epDisp.DispatchRuntime.MessageInspectors.Add( new MessageDiagnosticsInterceptor() );
}
}
}
}
The code above simply injects our custom inspector (defined above) into the runtime.
Setting up our WCF endpoint to use our Custom Service Host Factory
<%@ ServiceHost Language="C#" Debug="true"
Factory="MessageDiagnosticsInterceptors.CustomServiceHostFactory"
Service="MessageDiagnosticsInterceptorHost.Service1"
CodeBehind="Service1.svc.cs" %>
In order to use the customHostFactory you will need to provide an entry in your svc file which references that factory. You will see this in the ‘Factory’ line
As you can see creating an interceptor in WCF is not hard, but it does take a few files and it does take some setup work. I hope this helps someone.
Till next time,
Posted
02-03-2011 12:10 PM
by
Derik Whittaker