WCF Extensibility - Plugging in custom message processing logic
I have talked a lot about WCF's extensibility model and I never knew how much of the core functionality can be extended until a recent deep dive session with one of our customers developing on WCF. The client had a multi tier application with the middle tier using SOAP based messaging to communicate with the back end. They wanted to investigate using WCF for the middle tier and hence the deep dive session. They did the hard job of creating ServiceContract's for all the functionality the WCF middle tier service will expose. The customer had invested heavily in current pipeline that handled processing and dispatch of the incoming SOAP messages to the backend and wanted to reuse that. The ServiceContract they developed had lots of Operations and this posed additional overhead on the logic port. Their ideal solution would be to somehow transform the SOAP message coming in from WCF to a format matching the format as expected by the backend.
Basically they wanted to avoid implementing the huge ServiceContract and just plug in a small wrapper that will perform the transform. The first solution that comes to mind is to implement a "facade Contract" that had one generic operation that accepted and returned a SOAP message and that did the message transformation. This was an acceptable solution but this meant that clients generating proxy to this facade contract will not have all the Operations of the original contract. The customer also wanted to retain functionality provided by individual operations like addressing and additional custom header information for a required context that could be used by the backend that was lost when using one generic operation. They wanted to avoid serializing and deserializing incoming and outgoing arguments as they could be large and hence avoid the performance hit from such conversions.
Some extensibility points that come to mind for this scenario are IInstanceProvider and IOperationInvoker. IInstanceProvider was ruled out coz the need was to extend the actual Operation invoked on the instance not extend the way instances were created. Ideally we ended up with an extensibility model that used IOperationInvoker and IDispatchMessageFormatter and here is how it works.
- All Operations of the ServiceContract will have one input Message parameter and return a Message. Passing Message's as parameter/return value means that WCF runtime will not serialize/deserialize the Message and hence the user app needs to work with the Message.
- Have a dummy implementation of the ServiceContract that has no implementations for all the operations. Host this dummy instance as a singleton instance for the ServiceHost. In this solution, the dummy instance would never be invoked.
- Implement an IOperationInvoker to plug in to the operation invoke logic.
- Implement an IDispatchMessageFormatter to plug in to the serialization/deserialization logic of the incoming arguments and passing them to the IOperationInvoker.
Let’s see how this extensibility processes an incoming request.
- Runtime invokes custom IOperationInvoker to allocate memory for the input parameters. Since we have one argument, allocate memory for one single object.
- Runtime then invokes custom IDispatchMessageFormatter.DeserializeRequest and passes the input Message with the memory created in step 1. It’s in this step that the user will do the transformation from WCF to backend format. It then puts this transformed message to the memory created in step 1.
- Runtime passes the dummy instance, populated memory from step 2 to custom IOperationInvoker.Invoke. (or BeginInvoke for async)
- The custom IOperationInvoker will then pass the transformed message to the backend and get the return SOAP message from the backend.
- Custom IOperationInvoker creates an empty output array for out parameters (we have no out params in this case) and returns the message returned by backend.
- Runtime invokes custom IDispatchMessageFormatter.SerializeReply to serialize the outputs/reply passed in step 5. It’s in this stage that the extension will retransform the backend SOAP format to WCF reply Message.
- Runtime returns the reply Message back to the client.
As you can see, we never use the dummy instance implementation and reduced the need for the extra serialize/deserialize step in the contract implementation to invoke the back end. Result - Another happy customer :)
Since this extension has no effect on the way messages are received/sent by WCF's runtime, all the message and transport level security options continue to work. When this extension is coupled with IInstanceContextProvider, IInstanceProvider, Custom Channels we can practically replace the all major operations of WCF runtime.
A sample program showing off the above extensibility is attached.