Message based correlation with WF/WCF in .NET 3.5
When .NET 3.0 was released we got Windows Communication Foundation (WCF) and Windows Workflow Foundation (WF). These were great technologies, but they would have been even greater if they were used together. Unfortunately, Microsoft decided not to include this for .NET 3.0. However, with the release of .NET 3.5 we got the WCF Send and Receive activities that you can use in your workflows in order to implement a WCF service contract by using a workflow. This works great when you want to start a new workflow instance for every call made to your service. But when you want your existing workflow instance to receive the input of another service operation, you’ll need to do some correlation.
Microsoft solved this by involving the client. When the first operation that created your workflow instance succeeded, a header or a cookie is attached to the outgoing message containing the workflow instance ID and it is the clients responsibility so store this ID and again attach it to the message when it performs the second operation. This can get tricky if the client is stateless, or when the client that creates the workflow instance is not the same as the client who invokes the second operation. It also requires your client to have knowledge of implementation details of the service. If the client doesn’t know that it has to retrieve the instance ID from the reply it gets from the service and then use that same ID again for the second call, it’s all going to break apart. This isn’t much of a deal if you’re developing both the client and the server, but if the client is being developed by a third party it becomes a fair bit more complex.
Ideally you would want the message that is going into the second service operation to have a key value with which you can find the workflow instance back. It would be even better if that value is something that has business value as well, such as an OrderID, or CustomerID. This means you can talk about functional keys outside the service, but you can do the mapping of those functional keys to workflow instances within the implementation of your service.
Unfortunately Microsoft hasn’t provided this as an option. It is coming with .NET 4.0, but we’re not quite there yet and the programming model for WF 4.0 is going to be quite different when compared to .NET 3.5. We needed a solution for this problem now, rather than having to wait for .NET 4.0 to come out. So I set out to see if we could somehow do message based correlation with the WCF Send and Receive activities in .NET 3.5. I quickly learned that that wasn’t an easy task…
The implementation of the WCF Send and Receive activities relies heavily on WCF’s extensibility. In fact, Microsoft has made a whole new assembly named System.WorkflowServices which contains a whole bunch of classes that live in the existing System.ServiceModel and System.Workflow namespaces. One of the things they’ve added is called a context binding. The context binding basically does the whole cookie/header approach to solving the correlation problem. So I thought I’d write a WCF behavior that implements IDispatchMessageInspector that can inspect the messages that are being received at the service, fetch the instance ID from it and then add the required header to the message so that the existing infrastructure from System.WorkflowServices can pick it up and make the call to the right instance. However, it turns out that the message inspectors are almost at the bottom of the foodchain and the whole System.WorkflowServices infrastructure already decided to create a new instance for the second operation before my message inspector was invoked. So that wasn’t going to work…
After some more digging around using .NET Reflector I found an interesting class called WorkflowInstanceContextProvider. It was in this class that the message header (or rather a message property that was being fed with information from the header) was being read and the appropriate workflow instance was being attached to the operation context. Unfortunately the class was made internal, so I couldn’t inherit from it. It did however implement an interface, which is the standard WCF interface IInstanceContextProvider. So I decided to create my own IInstanceContextProvider implementation that would wrap an existing IInstanceContextProvider implementation, which in this case would be the WorkflowInstanceContextProvider. And to my surprise, this worked perfectly. I made a small test with a workflow that had two Receive activites. When the first one was invoked a CodeActivity made sure that the instance ID of that instance was assigned to a static member in my IInstanceContextProvider implementation. When the second call came in I would inject the value of that static member into the message by using the ContextMessageProperty class. To make my test better I used the input of the first operation as the output of the second operation and that test worked.
All I had to do now was to actually get the value to correlate on from the message, how hard could it be? Well, a bit harder than you would think. The IInstanceContextProvider interface provides to method that both receive the Message instance that is being processed. When you have a Message object you can read it using either the GetBody or the GetReaderAtBodyContents methods. This allows you to read the contents of the message body and do whatever parsing you need on them. The problem is that once you read a message, you can’t read the message again. This is done on purpose so you can use streaming messages. If you want to read the message twice you would use the CreateBufferedCopy method and work from there.
This is all good and well, but the interface I was implementing gave me only a reference to the message, but it did not give me a reference to the reference. What this means is that although you can change the message you receive, you can’t change the reference to the message so can’t create a new message and then assign it to your reference. Well, you can, but whoever called your method still has a reference to the original message. So if I was reading the message in my IInstanceContextProvider implementation it could not be read again further up the WCF stack. Eventually an exception would be raised saying that the message was already read and could not be read again.
So there I was. I had a solution for one problem, but now I had another problem which I couldn’t easily fix. That was until my colleague Marcel came up with a simple solution. The Message class I mentioned earlier also has a ToString method. This actually serializes the whole message into XML, regardless of whether it is a streamed or buffered message. When the message is streamed though, you will not get the body of the message, as explained here. When you call ToString you can still read the message later on. So now we have a solution that works, but only if you’re using buffered messages. If you use streamed messages things will break. We’ll probably implement a check in our implementation that verifies that you are using buffered messages when the behavior is applied, but that’s another problem…