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.
And after the little detour on the previous post about the IInteractiveChannelInitializer interface, we’re back on the channel layer, most specifically at the transports. Last time, we covered (extensively) the simplest kind of transport channel, a request channel. This time we’ll go to the other side of the communication and cover the reply transport channel (i.e., one which implements the IReplyChannel interface).
The reply channel is one which can be used in request / reply protocols (such as HTTP): the server receives a request, then sends a response back and the communication episode is finished. That is different from a duplex channel (in which after the original connection the server can send multiple “responses” to a single client request), or one-way channels (where the server receives a client request but doesn’t respond with anything). In WCF a reply channel is implemented using a RequestContext; when a new request arrives at the channel a new context is created and passed to the application; the application then uses the request context to send the response back to the client. That’s a better architecture than a simple “application receives message then returns response to the transport channel”, because with the request context the concurrency is improved (the transport can handle multiple requests to the application before a response is returned).
This post will describe the reply channel interface, and the request context. On the sample section, we’ll continue on the JSON-RPC scenario, this time writing the server part. Even though this is more complex than the request channel itself, since we already have the base channel implemented from the previous post, we’ll be able to reuse it and the reply channel itself won’t be too complex.
None. As with most channel extensibility points, there are no public implementations in WCF. There are many internal implementations, such as the one used by the HTTP transport.
The reply channel interface includes many variations of methods to receive a request and get a request context. The synchronous ReceiveRequest will block the calling thread until a request arrives (or a timeout expires) and then return a request context. The asynchronous BeginReceiveRequest and EndReceiveRequest will not block the request on the begin call; when a new request arrives the callback passed by the user is called, and the user can then call the end method to get the request context. If the timeout is elapsed, a TimeoutException will be thrown in those cases (by ReceiveRequest on the synchronous path, or by EndReceiveRequest on the asynchronous path).
To avoid dealing with exceptions, this interface also exposes the Try… pattern: TryReceiveRequest (synchronous) and BeginTryReceiveRequest / EndTryReceiveRequest (asynchronous) are similar to their try-less siblings, but instead of throwing an exception when the timeout expires and no request arrives, those methods will simply return false – and when a request arrives, the context is returned in an out parameter. Yet another alternative exposed by this interface is the Wait… pattern: WaitForRequest / BeginWaitForRequest / EndWaitForRequest – instead of dealing with exceptions, the method will simply return true or false if a request is available (and after that the user can call the synchronous ReceiveRequest method which won’t block and return the context immediately). When using the WCF service model, the method which will be called are the BeginTryReceiveRequest / EndTryReceiveRequest in a loop (this can be changed using the SynchronousReceiveBehavior).
The last member of the interface is the LocalAddress property, which gets the address where the channel is receiving the messages.
When the caller of IReplyChannel receives a RequestContext for a new request, they can use the RequestMessage property to get the incoming message. When the response is ready to be sent, they can call the Reply method (or the asynchronous BeginReply / EndReply pair). WCF will by default call the synchronous Reply method when returning responses to the client, unless changed by the DispatcherSynchronizationBehavior.
Like other channels, transport channels are added by using a channel factory (client) / or channel listener (server), which are created by a custom binding element. For reply channels, since they’re server-only, the binding element must override BuildChannelListener<TChannel> to return. The binding element corresponding to a transport channel must be derived from the TransportBindingElement class. Besides the normal properties from the base BindingElement class, the transport binding element also defines a Scheme property, which needs to return the URI scheme for the transport. The sample code below will show an example of defining a reply transport channel.
On the original post about transport channels, I mentioned one customer from the forums who wanted to create a service for the JSON-RPC protocol. The original posts dealt with the client part of such service, now we finally get to the server part with the reply channel.
For this sample, I’ll reuse some of the classes from the sample from the request channel post – especially the base channel which is essentially ready for both client and server usage, so if you haven’t already done so, I’d strongly recommend to read those posts.
So, on to the sample. Like in the transport, since we don’t have any service model support for the server side, we need to start with an untyped message contract. The service which implements this contract simply echoes whichever input is passed to it (at this time there’s no JSON-RPC yet, we’ll get there). We’ll then write the transport which can be used for this service using the same framing protocol we used before.
Now that what we want to accomplish is defined, let’s go to the transport. But before going further, here goes the usual disclaimer: this is a sample for illustrating the topic of this post, this is 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 (please let me know if you find a bug or something missing). I really kept the error checking to a minimum (especially in the “simple service” project and in many other helper functions whose inputs should be sanitized, to make the sample small. Also, I practically didn’t implement any timeouts in the channel (they’re mostly being ignored), otherwise this sample which is rather large would be even more complex.
First, the binding element. This is the same binding element as in the previous post, but I’m now overriding two more functions: CanBuildChannelListener<TChannel> and BuildChannelListener<TChannel>, which are used at the server side. At this point, we’ll support the IReplyChannel interface, and on BuildChannelListener we’ll return a new class, SizedTcpChannelListener.
The listener class inherits from ChannelListenerBase<TChannel>, which takes care of some of the boilerplate code for managing the listener life cycle – we only need to override the functions to open, close and accept channel (well, all of its variants). In the listener shown below, the constructor does some basic validation to ensure that the binding has the correct encoder (like in the request channel post, this transport requires the ByteStreamMessageEncodingBindingElement). Opening the listener is done by binding the socket to the local address and starting listening. Since those are fairly fast operations, even the asynchronous path uses the synchronous method in the socket, and returns a CompletedAsyncResult which is completed right away. The same for closing the listener, just close the socket. Then we get to the main operation of the channel listener: AcceptChannel (and its asynchronous counterparts, BeginAcceptChannel and EndAcceptChannel). The synchronous path is simple: the method calls Accept on the socket, then wraps it on a reply channel. For the asynchronous path, we’ll follow the “create an AsyncResult implementation which does the work and return it on Begin” which we used extensively on the post about request channels. The listener base class also has methods which follow the Wait… pattern, but since WCF doesn’t call them, I’ll leave them unimplemented in this sample.
Compared with other AsyncResult classes, the AcceptChannelAsyncResult class is actually simple. It doesn’t do anything complex, except dealing with ObjectDisposedException which can be raised if the socket is closed (which will happen if the listener is closed or aborted), and not return any channel at that point. As a recap, the constructor first calls the Begin operation on the socket. If the operation completed synchronously, the code will complete itself and store the result in the “channel” field.
And we’re finally at the transport channel layer. IReplyChannel defines a local property which we can return based on that the listener passes to the constructor. InitializeSocket is defined on the base class, which sets the socket in that class to be used for socket-related operations. But in order to give the information about the remote endpoint to the caller, this class also stores the socket instance in a private member, so when the message is decoded, we can add the RemoteEndpointMessageProperty object to it.
The synchronous operations are shown below. When a new message arrives (using the ReceiveMessage method from the base class), a new request context (to be shown later) is created and returned to the caller.
The asynchronous path uses new AsyncResult classes, mostly because we need to pass the timeout parameter from the Begin call to the request context (whch will only be available at the End call). I’ll skip those async result classes from this post, but they can be found at the code in the gallery.
And, to finish the channel, the request context class. The functionality is already implemented in the base channel, so the implementation of the SizedTcpRequestContext is fairly simple, merely delegating the calls to the reply channel, including the asynchronous operations.
The reply channel is ready, so we’re ready to try it. Let’s use similar requests than the ones which were used by the request channel sample. At this point the service will simply echo those requests back, but we can try it and make sure that the channel is working properly.
And that’s it. But if we really want to make it useful, we need some service model support for that, so let’s add them now.
The Message-based server was enough to show that the transport is working, but we really need to add the runtime pieces if we want to be able to make it user-friendly. We want to be able to write a service like the one below, where we’re dealing with “regular” CLR types.
Like the client, we first need a message formatter, more specifically an IDispatchMessageFormatter implementation. That’s the component which knows how to convert between the Message object and the typed CLR parameters for the operations at the server side. Like on the client side, we’ll also use the NewtonSoft Json.NET library, and the helper operations we used in that example to convert between message objects and the JObject instance which represents the JSON in the request / reply. For the code of the helpers, check the service model post on the request channel, or the code in the gallery.
Next, we need the correlation between the “id” member in the request and the reply. The operation doesn’t need to know any of the JSON-RPC details, so we’ll use a message inspector which gives us the correlation between request and reply. In AfterReceiveRequest we fetch the request id, and return it to be passed to the BeforeSendReply method. On this method we receive the id as the correlation state parameter, add it in the JSON, and encode the message with the id member.
Those classes had their counterpart in the client side, but at the server we need some additional help. First, when a request arrives we need to identify which operation will “serve” that request, and for that we need an operation selector, or more specifically an IDispatchOperationSelector. That’s likely the simplest of the implementations, as we simply retrieve the “method” member in the incoming request, and that is returned so that WCF knows which of the service operations to invoke.
That’s pretty much it for the “normal” case. But the JSON-RPC protocol also specifies how errors are returned, so it’s nice to have an error handler (implementing the IErrorHandler interface) which will convert exceptions thrown in the operation to the format expected by the protocol. In this implementation we’re first checking if the exception thrown is JsonRpcException (the custom exception type we introduced in the post about the request channel). That’s a nice way to give the user control over the result. For other exceptions, we map their type and message to the JSON which is returned in the “error” member of the response.
And, finally, to glue everything together, we’ll use the JsonRpcEndpointBehavior, implementing the functionality on the ApplyDispatchBehavior method. There’s just one more thing which the behavior does: it changes the contract filter in the endpoint dispatcher to map all the messages to this dispatcher – since we don’t use addressing in this transport, and we’re dealing with it ourselves (with the operation selector), we need to tell that to WCF.
Good to go now, we just need to update the test program to check that everything is in place.
And we’re done with the reply channel, including service model support. That means that we can now implement a service with a custom protocol using WCF.
I’ll finish the section about transport channels (and the whole channel layer) with a quick post about duplex channels.
[Code in this post]
[Back to the index]