Yossi Dahan [BizTalk]


Monday, January 10, 2011

WCF behaviour to track inline WCF requests to BAM

In the project I’m on, as I’ve mentioned a few times, we have an elaborate infrastructure to log our activity using BAM.

This relies largely on a set of pipelines with custom components as its the most efficient and non-intrusive approach in my view.

The problem though, is that this approach does not cover ‘inline sends’ to services (which we don’t do too often, but have in a couple of places); these calls do not make use of pipelines and as such cannot benefit from this infrastructure and our logging story is, therefore, lacking.

Last week I was set to change that.

The approach I’ve taken is to develop a custom WCF endpoint behaviour that would be configured on a client’s endpoint and would log the contents of the messages exchanged with the service to the BAM infrastructure using our existing API.

I decided to run the behaviour on the client as in our case, in almost all cases, the client would be a process on the BizTalk server and so it makes most sense, but, more importantly, we might not always have control over the service side, for example when calling 3rd party services.

The structure of our ‘log’ is built using 4 BAM activities – one represents the end-to-end scenario, one represents a call to a service, one represents a message in that call (so, generally speaking, the request or the response) and one represents a part of such a message, i.e. an individual parameters (in or out).

The behaviour would assume the top level activity was already created and it’s key (a GUID) is provided through a message header (if one wasn’t create, a warning would get logged and the behaviour would do nothing), but would need to create instances of the other three activities as needed.

It also needs to work asynchronously where possible to introduce as minimal delay as possible to the message exchange between the client (BizTalk) and the service.

The implementation consists of a couple of classes -

  • A client-side message inspector (implementing IClientMessageInspector) – this is where the bulk of the work is done – BeforeSendRequest and AfterReceiveReply provide excellent hooking point to handle the request going to the service and the response being returned; My implementation of these i designed to do the bulk of the work asynchronously so that the actual message is sent in parallel to the code tracking it which does require grabbing a copy of the message, which does add some overhead, but hopefully that’s acceptable.
  • A class to act as the endpoint behaviour (and and an extension element) allowing me to attach the inspector to an endpoint through both code and configuration.

The latter is very simple – I’ve created a class – TrackingBehaviour which inherits from BehaviorExtensionElement and implements IEndpointBehavior, here’s the complete code -

public class TrackingBehaviour : BehaviorExtensionElement, IEndpointBehavior
public TrackingBehaviour(){}
public TrackingBehaviour(string Service)
this.Service = Service;
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
//add the tracking inspector to the inspectors configured for this endpoint
clientRuntime.MessageInspectors.Add(new TrackingMessageInspector(Service));

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
throw new NotImplementedException("This behaviour supports client side only");

        public void Validate(ServiceEndpoint endpoint)

        #region BehaviorExtensionElement 

        public override Type BehaviorType
get { return typeof(TrackingBehaviour); }

        protected override object CreateBehavior()
return new TrackingBehaviour();

        [ConfigurationProperty("Service", IsRequired=true)]
public string Service
return (string)base["Service"];
base["Service"] = value;

As I didn’t intend to support service side I’ve left ApplyDispatchBehavior to throw a NotImplementedException, but have implemented ApplyClientBehaviour to attach my message inspector to the endpoint’s behaviours.

The only other piece of code in the class worth mentioning is the Service property – this is exposed as configuration to the developer to provide the name of the service being tracking, if not provided the message inspector examines the soap action and tries to figure that out, but this configuration entry allows the developer to provide a more meaningful/friendly name

Having declared the configuration element the endpoint behaviour can now be attached to a client’s endpoint easily through configuration -

You start by declaring the extension in the extensions element of system.serviceModel configuration section-

<add name="Tracking" type="TrackingBehaviour, TrackingBehaviour, Version=, Culture=neutral, PublicKeyToken=314bd7037656ea65"/>

You continue by declaring a behaviour, using this extension -

<behavior name="TrackingBehaviour">
<Tracking Service="Test"/>

Then you can attach this behaviour to any client end point -

<endpoint address="http://localhost:8731/Design_Time_Addresses/ConsoleApplication1/Service1/"

The actual work, as I’ve mentioned, is done in the message inspector itself, so let’s look at that -

Implementing IClientMessageInspector the two methods to implement are BeforeSendRequest and AfterReceiveReply and both are provided with the WCF message being worked at (the request or the service’s reply).

The exact implementation really depends on the specific tracking solution, in our case we’ve already assume the end-to-end activity has been created, so the code for BeforeSendRequest looks for the custom message header with the tracking id.

If not found a FaultException is thrown, if found the tracking ID is kept and will be used to link any created activities to the end to end scenario.

Next the code tries to figure out the name of the service and the name of the operation called, as this is logged with the activity. The latter is always taken from the soap action header (request.Headers.Action), the former might be configured at the behaviour level, but if not is resolved from the soap action as well.

An activity ID is generated for the call and then our BAM API helper is called with all these pieces of information, this would create a BAM activity with the generated id and the service and operation names and would add a reference to the end-to-end activity using the id provided, I then take the message contents (I use request.ToString()) and use QueueUserWorkITem to call method that will perform the actual tracking asynchronously.

As a return value from the inspector’s method I use a class that holds the context – the id of the end-to-end activity and the generated id for this activity just created, this will be passed by the framework to the AfterReceiveReply method when processing the reply so that it has the context needed to update and end the activity.

In the tracking method, which I called asynchronously, I use XmlReader to find the Body of the SOAP envelope; the first child of the SOAP body would be the method element so I skip that as well, as then I consider every child of that to be a parameter sent to the service that needs to get tracked and so I track each one using the OuterXml property.

I keep the name of the method element and that’s what I’m looking for to know I’ve processed all the parameters -

            StringReader sr = new StringReader(messageTrackingDetails.Message);

            //The following code assumes a specific structure of the SOAP Envelope. 

            //moving to different formatting may cause this to break or behave unexpectadly

            using (XmlTextReader reader = new XmlTextReader(sr))



                //nothing we care about before the soap:Body element

                reader.ReadToFollowing("Body", SOAP_XML_NAMESPACE);

                //the first child of the body is the operation element

                reader.ReadStartElement();//this would be the operation, so read that

                reader.MoveToContent();//ensure we read to next content

               //keep the name of the operation, when hitting the end element of that name we know we've completed reading the parameters

                string messageName = reader.LocalName; 

                if (string.IsNullOrEmpty(messageName))

                    messageName = "unknown";

                //create the message activity

                MainActivity.AddMessage(MainActivity.eStreamType.BufferedEventStream, messageTrackingDetails.TrackingId, 

                                                           messageId, messageName, messageTrackingDetails.ExternalActivityId);

                while (reader.Read())


                    if (reader.LocalName == messageName && reader.NodeType == XmlNodeType.EndElement)


                    if (reader.NodeType != XmlNodeType.Element)

                        continue;//we're not interested in anything other than elements


                    trackParts(messageTrackingDetails, messageId, reader.LocalName, reader.ReadOuterXml());



The method to process the reply (AfterReceiveReply) is slightly simpler as all the contextual information exists in the correlationState parameter, which is the object returned by the BeforeSendRequest and so it really only needs to end the activity started earlier (id given in the context) and track the response message using the same method explained above.

And that’s all there is to it, granted you need to have you tracking solution in place, but implementing client side tracking using WCF behaviours proved very easy and powerful.

Labels: ,


  • Mind if post the code sample for this? It is really hard to read the code in this post. Thanks

    By Anonymous Anonymous, at 10/01/2011, 16:09  

  • I'm sorry, but I don't have a sample code, and I can't publish the actual code I've developed (as I don't 'own' it), when I get some time I'll try to tidy up the post as live writer wasn't kind to me with it (looked ok on my machine is the famous developer's excuse, right?!)

    By Blogger Yossi Dahan, at 10/01/2011, 16:32  

Post a Comment

<< Home