tags:

views:

225

answers:

0

I am presently converting some existing .NET 3.X WCF code (written by someone else) to .NET 4.0. Of particular interest is a WCF service that implements a restful COMET behavior by using a specialized dispatcher (ChannelDispatcherBase) and behavior (WebHttpBehavior). This code works by deferring a WCF response from the server until certain criteria are met, at which point it allows WCF to make a response, which is often much much later. Until this happens, WCF holds open the connected port indefinitely. Without going into the wherefores and how-tos of why the company uses this approach, it suffices to say that in the .NET 3.X world it worked admirably.

However...

Enter .NET 4.0 - though the code compiled without error it no longer runs. The problem appears at the latter stages of the COMET behavior when the server goes to make a deferred reply. When it goes to make a reply is uses IDispatchMessageFormatter.SerializeReply(...) to craft a reply System.ServiceModel.Channels.Message object. The IDispatchMessageFormatter the code is using to make this reply was acquired earlier in two stages:

First, when the ApplyDispatchBehavior(...) method fires on the behavior a DispatchOperation is acquired:

public class OurBehavior : WebHttpBehavior { . . public override void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { . . DispatchOperation dispatchOperation = endpointDispatcher.DispatchRuntime.Operations[...]; } }

Second, when the ServiceHost's Opened handler fires, the code acquires a formatter from DispatchOperation and caches it:

public void HostOpenedHandler(object sender, EventArgs e) { . . IDispatchMessageFormatter formatter = dispatchOperation.Formatter; . . }

With the formatter in hand, much much later when a deferred reply is crafted, the code makes a reply like so:

Message replyMessage = formatter.SerializeReply(version, outParams, retVal);

This is where the code blows up with a null reference exception. Here's the stack trace:

----> at System.ServiceModel.Dispatcher.MultiplexingDispatchMessageFormatter.SerializeReply(MessageVersion messageVersion, Object[] parameters, Object result) at System.ServiceModel.Dispatcher.CompositeDispatchFormatter.SerializeReply(MessageVersion messageVersion, Object[] parameters, Object result) at

If you reflect into MultiplexingDispatchMessageFormatter.SerializeReply you find this:

public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result) { OutgoingWebResponseContext outgoingResponse = WebOperationContext.Current.OutgoingResponse; . . . }

The very first line of code in that method includes the call ‘WebOperationContext.Current’. I have confirmed that WebOperationContext.Current is in fact null before we call this method - hence the null reference exception. Of course WebOperationContext.Current is null for us because we're crafting a reply that is outside the scope of a traditional WCF method call. Apparently Microsoft has made assumptions that WebOperationContext.Current would always be safe which is not true for us.

Since I have total access to the WCF server object graph, it seems reasonable that I should be able to cause WebOperationContext.Current to be set before I call the formatter. This appears, however, to be very poorly documented. After googling the universe it seems like you really need to make an OperationContextScope which will set up WebOperationContext for you. Something like this:

using (OperationContextScope ctx = new OperationContextScope(IContextChannel)) <--- magic part { // OperationContext.Current shouldn't be null now and I can call formatter }

If this is correct, then it seems like I need an IContextChannel to pass to the OperationContextScope .ctor. I wonder if I can acquire an IServiceChannel object (since it inherits from IContextChannel) and pass that to the OperationContextScope .ctor. If that's true, where or where do I acquire an IServiceChannel?? Though I am not totally unfamiliar with WCF I am finding the object graph in this code far exceeds my expertise with WCF, and I'm stuggling with where to look. I have found some articles that suggest using a temporary channel factory like so:

ChannelFactory factory = new ChannelFactory(...); IServiceChannel channel = factory.CreateChannel(); using (OperationContextScope ctx = new OperationContextScope(channel)) <--- magic part { // OperationContext.Current shouldn't be null now and I can call formatter }

But this 'feels' off - creating a channel just to make a formatter work. Plus I tried this in the code and I'm not even sure what to pass to the ChannelFactory .ctor and the few things I tried the factory threw an exception to the effect of "Attempted to get contract type for IServiceChannel, but that type is not a ServiceContract, nor does it inherit a ServiceContract."