WCF Connections and Messages Tracer

September 11, 2012

This article came after detection BAD WCF client functionality. I created basic Windows Service using for communication the WCF platform.

But I created 2 big mistakes:

  • I set the parameters values  that defined the call Open and Receive timeout was unreasonable
  • In case of the Register method, my first called method by the clients after they open channel to the server, throw exception but yet didn’t closed the client session. 

So, I understand that timeout parameters are more than nice to have and that I cannot relay on the client to close the channel on major exception, it’s all on me!!!

So now I had two problems:

  1. What should be the timeout, still my service clients distributed over the net and my functionality sometimes takes time to finish
  2. How can I “cut” the client connection

What should be the Timeout???

So, like most of the the things that I understand, i should run some tests to understand two major things about my Server methods.

  • How match time the call handled on my side (Server side) and some percentage for the actual channel line activity
  • What are my message size

For that i added the next behaviors:

Behaviors Implementation:

For the Host (Listener) Endpoint(s) behavior side we will add the next Implementations:

  • IInstanceContextInitializer:

    Defines the methods necessary to inspect or modify the creation of InstanceContext objects when required. (see )

  • IChannelInitializer:

Defines the interface to notify a service or client when a channel is created. (see )

By adding those Interfaces we’ll be able to monitor the Client Connection Status, (Online, Faulted or Disconnected)

I created wrapper to the IEndpointBehavior interface and set those implementation on it’s ApplyDispatchBehavior function.

IEndpointBehavior implementation

   1: public class TracerEndpointBehavior : IEndpointBehavior

   2:    {

   3:        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

   4:        {

   5:           /*NOOP*/

   6:        }

   7:  

   8:        public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)

   9:        {

  10:            /*NOOP*/

  11:        }

  12:  

  13:        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)

  14:        {

  15:            endpointDispatcher.ChannelDispatcher.ChannelInitializers.Add(new ClientTrackerChannelInitializer());

  16:            endpointDispatcher.DispatchRuntime.InstanceContextInitializers.Add(new ClientTrackerInstanceContextInitializer());

  17:        }

  18:  

  19:        public void Validate(ServiceEndpoint endpoint)

  20:        {

  21:            /*NOOP*/

  22:        }

  23:    }

As you can see I added to the “endpointDispatcher” behaviors to be notified when the channel is been initialized and when the context been initialized

IInstanceContextInitializer & IChannelInitializer implementation

   1: class ClientTrackerChannelInitializer : IChannelInitializer

   2:     {

   3:         internal static int ConnectedClientCount = 0;

   4:  

   5:         public void Initialize(IClientChannel channel)

   6:         {

   7:             TracerManager.Instance.AddClient(channel);

   8:             channel.Closed += ClientDisconnected;

   9:             channel.Faulted += ClientFaulted;

  10:         }

  11:  

  12:         static void ClientDisconnected(object sender, EventArgs e)

  13:         {

  14:             TracerManager.Instance.SetStatus(((IClientChannel)sender), SessionStatus.Close);

  15:         }

  16:  

  17:         static void ClientFaulted(object sender, EventArgs e)

  18:         {

  19:             TracerManager.Instance.SetStatus(((IClientChannel)sender), SessionStatus.Faulted);

  20:         }

  21:     }

*** We set events implementation both on Closed & Faulted events

   1: class ClientTrackerInstanceContextInitializer : IInstanceContextInitializer

   2:    {

   3:  

   4:        public void Initialize(IClientChannel channel)

   5:        {

   6:            TracerManager.Instance.AddClient(channel);

   7:        }

   8:  

   9:        static void ClientDisconnected(object sender, EventArgs e)

  10:        {

  11:            TracerManager.Instance.SetStatus(((IClientChannel)sender), SessionStatus.Close);

  12:        }

  13:  

  14:        static void ClientFaulted(object sender, EventArgs e)

  15:        {

  16:            TracerManager.Instance.SetStatus(((IClientChannel)sender), SessionStatus.Faulted);

  17:        }

  18:  

  19:        public void Initialize(InstanceContext instanceContext, System.ServiceModel.Channels.Message message)

  20:        {

  21:            OperationContext context = OperationContext.Current;

  22:            MessageProperties messageProperties = context.IncomingMessageProperties;

  23:            SessionID id = new SessionID(context.SessionId);

  24:            TracerManager.Instance.SetIPAddress(id.GetHashCode(), (

  25:                System.ServiceModel.Channels.RemoteEndpointMessageProperty)messageProperties["System.ServiceModel.Channels.RemoteEndpointMessageProperty"]);

  26:        }

  27:    }

 

*** The Session ID is the indicator for the connection, connection <-> session

  • IDispatchMessageInspector:

Defines the methods that enable custom inspection or modification of inbound and outbound application messages in service applications.(see )

By adding this Interface we’ll be able to monitor the Incoming and outgoing messages (Body) size.

I created wrapper to the IServiceBehavior interface and set those implementation on it’s ApplyDispatchBehavior function.

IServiceBehavior implementation

   1: public class TracerServiceBehavior : IServiceBehavior

   2:    {

   3:        public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

   4:        {

   5:            /*NOOP*/

   6:        }

   7:  

   8:        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)

   9:        {

  10:            foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers)

  11:            {

  12:                foreach (EndpointDispatcher epDisp in chDisp.Endpoints)

  13:                {

  14:                    epDisp.DispatchRuntime.MessageInspectors.Add(new DispatchMessageInspector());

  15:                }

  16:            }

  17:        }

  18:  

  19:        public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)

  20:        {

  21:            /*NOOP*/

  22:        }

  23:    }

Here for each EndpointDispatcher we set our new implementation on the Message Inspectors

IDispatchMessageInspector implementation

   1: public class DispatchMessageInspector : IDispatchMessageInspector

   2: {

   3:     public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)

   4:     {

   5:         OperationContext context = OperationContext.Current;

   6:         MessageProperties messageProperties = context.IncomingMessageProperties;

   7:         Console.WriteLine("IDispatchMessageInspector.AfterReceiveRequest called.");

   8:         double size = 0;

   9:         try

  10:         {

  11:             if (!request.IsFault && !request.IsEmpty)

  12:             {

  13:                 using (var messageBodyReader = request.GetReaderAtBodyContents())

  14:                 {

  15:                     var messageBody = messageBodyReader.ReadOuterXml();

  16:  

  17:                     size = RetrieveMessageBodySize(messageBody);

  18:                     RebuildMessage(ref request, messageBody);

  19:                 }

  20:             }

  21:             TracerManager.Instance.AfterReceiveRequest(context.SessionId, request.Headers.MessageId, size, DateTime.Now, request.Headers.Action);

  22:  

  23:         }

  24:         catch (Exception e)

  25:         {

  26:         }

  27:         return null;

  28:     }

  29:  

  30:     public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)

  31:     {

  32:         OperationContext context = OperationContext.Current;

  33:         MessageProperties messageProperties = context.IncomingMessageProperties;

  34:         double size = 0;

  35:  

  36:  

  37:         try

  38:         {

  39:             if (!reply.IsFault && !reply.IsEmpty)

  40:             {

  41:                 using (var messageBodyReader = reply.GetReaderAtBodyContents())

  42:                 {

  43:                     var messageBody = messageBodyReader.ReadOuterXml();

  44:  

  45:                     size = RetrieveMessageBodySize(messageBody);

  46:                     RebuildMessage(ref reply, messageBody);

  47:                 }

  48:             }

  49:             TracerManager.Instance.BeforeSendReply(context.SessionId, reply.Headers.RelatesTo, size, DateTime.Now);

  50:  

  51:         }

  52:         catch (Exception e)

  53:         {

  54:         }

  55:     }

  56:  

  57:     private double RetrieveMessageBodySize(string messageBody)

  58:     {

  59:         double bodySizeInBytes = Encoding.UTF8.GetByteCount(messageBody);

  60:  

  61:         return bodySizeInBytes;

  62:     }

  63:  

  64:     private static void RebuildMessage(ref Message message, string messageBody)

  65:     {

  66:         if (message.IsFault || message.IsEmpty) return;

  67:  

  68:  

  69:         var meesageBodyDocument = new XmlDocument();

  70:         meesageBodyDocument.LoadXml(messageBody);

  71:  

  72:         ///First create the new clone message and set the Body & Version

  73:         var replacementMessage = Message.CreateMessage(message.Version,

  74:                                                null,

  75:                                                new XmlNodeReader(meesageBodyDocument.DocumentElement));

  76:  

  77:         ///Set the clone message headers

  78:         replacementMessage.Headers.CopyHeadersFrom(message);

  79:  

  80:         ///Finaly add the missing to the cloned message

  81:         foreach (var propertyKey in message.Properties.Keys)

  82:         {

  83:             replacementMessage.Properties.Add(propertyKey, message.Properties[propertyKey]);

  84:         }

  85:  

  86:         ///Change the pointer

  87:         message = replacementMessage;

  88:     }

  89: }

As you can see on both methods (AfterReceiveRequest & BeforeSendReply) we call the RetrieveMessageBodySize method to retrieve the message body size, in bytes. But because we poll out the message body context

var messageBody = messageBodyReader.ReadOuterXml();

We actually reading the message and we cannot pass it over to the next step, and because of it we must rebuild the message (deep clone)

private static void RebuildMessage(ref Message message, string messageBody){...}

So now for what should be the timeout we can run test with max timeout size for the connectivity and functionality and find the average add “X” factor and set it!!!

How can I disconnect client form the server listener side???

As you can see on the ClientTrackerInstanceContextInitializer (IInstanceContextInitializer implementation) we save the client channel IClientChannel, one of the methods that this interface reveals is Abort().

Now how and when to use it? it’s by your own logic!

It can be based on Authentication/ Security/ Performance / …

On my viewer it done by pressing a button

 

In my demo all the communication data stored and managed on TracerManager class (please see the attached code)

From this manager when can see the messages progress and state, also the channels state and functionality.

 

WCF Tracer window

wcftracer

The Application opened by clicking on the Notification Icon on the status bar

On the header we show all the opened listener by the Host

After it we have the clients list and status, also we have the possibility to close the client connection from there “Abort Connection” button (if still open)

At the end we have the list of all the messages that pass between the Server listener and his Clients:

From witch client (by the Session ID)

Witch method called

When we got the request

When we send it

Incoming message size

Outgoing message size

The total time the server handled this request

 

I hope this article assist you.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

3 comments

  1. seo toolsMarch 4, 2013 ב 13:38

    Hello to every one, it’s really a good for me to go to see this web site, it includes helpful Information.

    Reply
  2. ChinnApril 19, 2013 ב 14:47

    This paragraph gives clear idea for the new people of blogging,
    that truly how to do running a blog.

    Reply
  3. RhettIWiemanJune 14, 2016 ב 20:00

    excellent submit, very informative. I’m wondering why one other experts on this sector will not notice this.
    You must continue your writing. I am sure, you possess an excellent readers’ base already!

    Here is my web-site RhettIWieman

    Reply