This post is part of a series about WCF extensibility points. For a list of all previous posts and planned future ones, go to the index page.
This post takes again a detour from the normal flow of the series as I’ve seen lots of requests for this scenario – the ability to use extensibility points which were added in Silverlight 4 in a Silverlight 3 (and Windows Phone 7) application. In SL4, all the client behavior classes (IEndpointBehavior, IContractBehavior, IOperationBehavior) along with the runtime interfaces used to interact with the WCF runtime (IClientMessageInspector, IClientMessageFormatter, IParameterInspector, IClientOperationSelector) were added to the platform. For Silverlight applications it just meant that the developer who needed that extensibility needed to move to SL4 (and since most of the Silverlight installations are auto-upgraded to the latest released version, the client base which could consume those new applications would be fairly large). Windows Phone developers, however, are stuck with the SL3 API, and many scenarios (such as custom serialization which I mentioned in a previous post) are quite hard to accomplish.
The primary extensibility which exists in SL3 (and SL2) is to write a custom channel. With a custom channel you can actually do pretty much anything in WCF – you have total control over the Message objects which are being sent / received from / by the client. However, writing a custom channel is really hard – besides working with directly with messages, you also need to implement all of the channel boilerplate, worry about channel shapes – custom channels is one of the less used features in WCF for a good reason, which is why most of the “nice” extensibility points were added at the first place.
A well-written custom channel, however, is a very powerful tool, and as Yavor Georgiev and Matthew McDermott wrote in their blogs, it can be used to implement a simple message inspector (in their posts they used it to work around a known issue in SL3 / SL4 / WP7). The idea is interesting, so I decided to expand on their sample and add the other extensibility points (except IClientOperationSelector because, as I’ve mentioned before, I don’t think it’s worth the hassle). And to validate that the new extensibility points could be used in a real scenario, I decided to try the same custom serialization example I used for Silverlight 4.
Yavor / Matthew sample had an implementation of a custom channel that exposed a message inspector. Here I modified it a little to enable two more features: correlation state (where a call to AfterReceiveReply can receive an object which is returned by BeforeSendRequest), and the ability to have multiple inspectors (in their example they only had one). The idea is that you add a binding element right before the transport element in a custom binding, and as the Message object passes through the channel stack, right before it reaches the transport, the inspectors are invoked to inspect / modify the message. The only difference between the implementation and the “real” one is that the IClientChannel parameter for BeforeSendRequest is not set.
And before I dive into code, the usual disclaimer: this is a sample, not production-ready code. I tested it for a few contracts and it worked, but I cannot guarantee that it will work for all scenarios – actually, I can guarantee that it won’t work for many scenarios, see missing features list below. For simplicity sake it does not do a lot of error handling which should be present in a production code.
The implementation of the custom channel starts with a class derived from ChannelBase and IRequestChannel. The original sample only supported this channel shape (IRequestChannel), but that’s ok for most scenarios since this is the most common shape (used in request / reply calls, which is the default for HTTP transport, which is the only transport supported in WP7). Since the call is asynchronous, I’ve used the same technique described at another post for chaining the calls and passing some additional information (the correlation state) to the other side.
The next step is to create a channel factory which knows how to create that inspector channel. The implementation is trivial, with many methods simply delegating to the inner factory (they’re omitted here for brevity).
Finally we need a public binding element which can be added to the binding. This implementation does some minimal validation, and in case where there are no inspectors registered in this binding element it simply skips completely the inspector channel and just return what would have if this binding didn’t exist.
And that’s it for the inspector.
The message inspector was somehow easy to be implemented (or as easy as writing a custom channel can be) because the parameter to the inspector methods are of the same type that the channels deal with: Message. Formatters and parameter inspectors, however, deal with the CLR objects, and at the point where the message arrives at the channels, those objects have already been consumed (for outgoing messages) or have yet to be decoded (for incoming messages) by some internal WCF code. The solution for this issue was to undo what WCF had already done (i.e., convert back from a message into the operation parameters, or from the incoming message to the operation results before handing them back to WCF. Another complication is that the formatter and the inspectors are bound not to the endpoint but to individual operations, and the channel applies to the whole endpoint, so we need a way to filter the messages (and the extensibility interfaces) on a per operation basis.
Solving the problem of the formatter / inspectors binding to the operations requires passing the endpoint contract to the channel – this way it can map each incoming message to the proper extensions. In this sample I implemented the formatter and the parameter inspectors as mappings between an OperationDescription object and the corresponding extension. Also, since there’s no DataContractSerializerOperationBehavior (in SL3 or SL4), I added a new class, DataContractSerializerEndpointBehavior which can be used to replace the serializer on a per type basis (currently it has to be applied to the whole endpoint, but it can be changed to be bound to the operation as well).
The mapping between the operations and the extensions, done at the channel creation, iterates through the operations of the endpoint contract passed to the class, and maps the Action property of the outgoing message to the appropriate extensions.
When an outgoing message arrives at the channel, if there are any formatters or parameter inspectors for its operation, then the code does a “reverse formatting”, unwrapping the operation parameters from the (WCF-packaged) message. It then passes the parameters to any registered inspectors, and then calls the formatter to wrap them back into a Message object. On incoming messages the code is similar – first the message is unwrapped with the given formatter, then passed to any parameter inspectors, and finally it’s wrapped again to be handed over to the rest of the WCF stack.
One thing to notice is that this channel must be the first one in the stack – to receive the message right after WCF formatted it, so the operation description still matches the message body – if some other channel had modified the message, the code would not work. This is enforced at the binding element responsible for creating the factory which creates this channel.
I’ll skip the channel factory and the other binding element methods, as they’re similar to the ones for the message inspector.
Those are the features which I know don’t work right now:
Those I did not test, and I’d be surprised if they worked:
Please let me know if there is any missing feature which is not listed above and I can update this list.
And now I’ll revisit the custom serialization for Silverlight which I posted before. The server code is exactly the same, and I’ll show the main differences in the client code:
The code for calling the service is the same as before.
And that’s it. In the [Code in this post] link below you can find the full solution with not only this scenario, but also some other tests for the extensibility interfaces.
[Code in this post]
[Back to the index]