When we were defining the features that the “Add Service Reference” (ASR) dialog for Windows 8 Metro-style applications would have (so that those apps could use typed proxies to consume SOAP services), one of the points where there was some confusion was what the tool should generate for duplex services. Should continue with the “classic” mode where clients needed to pass a InstanceContext reference for the callback contract, or could we use the mode added by the Silverlight version of ASR, which provided a nice event-based interface so that the developers didn’t have to worry about instance contexts or new classes that implement callback interfaces. This post will show a small history of the event-based duplex callbacks, and why we decided to add it for the Windows 8 version of ASR.
The majority of cases of WCF services are simple request/reply contracts. We define interfaces with a bunch of operations which take some inputs and return (or not) some result to the caller.
So far ASR / svcutil has always been happy, generating a client which can consume that service:
But some people decided that there are some cases where the client shouldn’t have all the control – if the service wants to talk to the client, it shouldn’t wait for the “master” client to initiate something. So we introduced duplex contracts (a pair of contracts connected by the CallbackContract property in the ServiceContract attribute), where both the client and the server can start a request to the other party. In the example below, if preparing an order was a very time-consuming operation (possibly requiring manual intervention), the server could take the task, and later notify the client that the order was ready. And svcutil / ASR was still happy about it…
And svcutil / ASR was still happy about it…
Now, if the client wants to be notified of any calls which are made, it needs to, when creating the proxy to the service, to pass an implementation of the callback interface (wrapped in an InstanceContext, I frankly don’t know why, but that’s another story). When the service wants to make a call to the client (a.k.a., a callback call), it would get a reference to the callback interface, and simply call it locally. WCF would then through the magic of proxying, serialization and deserialization, send the call over the wire, and at the client side it would call the implementation the client provided. Notice, it’s WCF who calls the callback interface, the user does not initiate any calls (this is important).
And for years people have used it, simply because there was no other way, but in most cases, the implementation of the callback interface (the “// do something”) would end up calling methods on the client class where the logic was located.
Silverlight was a new platform for which we ported WCF, and with its limited subset of the framework, it presented us both with some challenges and opportunities. Since the clients (running on browser) could not “listen” to incoming requests from the services (security / sandboxing issue), in order to create a duplex-like environment we implemented a polling mechanism (a.k.a. polling duplex), where the client would send, in addition to the “normal” requests to the service, another one (hidden from the developer, controlled by our code) which the service could use to send information back to the client. If the service had something to send (i.e., if the server wanted to “call an operation” on the client), it would use the response of the HTTP request to send it. That worked out quite well, but since there was no “response to the response”, we had to force all the callback operations to not have any return values (in other words, only operations marked with (IsOneWay = true) could be used in callback contracts.
That seemingly problematic restriction turned out to be a blessing – since we knew that we didn’t need to return anything, we could change the generated client to send events *for the callback operations* (and this is the big point which is causing the confusion in this thread), so the client didn’t need to create a new class to implement the callback interface – we could do it for them! So all the logic on the client code could be self-contained, no need to deal with InstanceContext (what is an instance context anyway?), and no need to create additional classes.
And lo and behold, people really loved it. We’ve got requests to add this feature to the “full” framework, which we can’t (at least not in a generic way) because callback contracts in general don’t have to be one way (we could do it only if the callback contract followed that rule, but that’s another story). The feedback was so positive that when we added support for TCP on Silverlight 4, even though we could at that point lift the restriction about one way callback contracts (because TCP is a full-duplex protocol) we didn’t, and people didn’t complain about it at all.
While people really loved having their callback calls delivered to them via events, people hated having to do asynchronous calls – the number of rants, complaints, pouts, etc. that we received for completely removing synchronous operation calls on Silverlight was huge. It was a conscious decision by Microsoft – UI programming has to be done in a way that doesn’t block the UI thread, and synchronous network calls would do it. We wanted Silverlight to be beautiful, and we wouldn’t let the developers spoil the cute baby that we were delivering to the world. But the developers had a point – writing asynchronous calls is really hard. If you once could do something like
With async-only calls, we’d need to break this method in two different ones, possibly losing any local variables which we wanted to use (closures and lambdas still aren’t dominated by the masses), and also having threading issues (we can only update UI controls on the UI thread; the callbacks to Begin/End async operations often arrived on a background thread). Life was hard.
But someone had a brilliant idea (I’m not being sarcastic, I really loved it) that the compiler could make the life of the developer easy for asynchronous calls. Instead of forcing the developer to break down the methods, and worry about cross-thread marshaling of data, what if the compiler itself did it? And with the await / async keywords, at least 99% of the reason for the complaining would disappear, and the following code would be written and look (almost) identical to the original one:
So everything is peachy now, we can finally move away from the synchronous pitfalls of hanging applications, and everyone will be happy writing apps for Windows 8, that OS will take over the world and Metro will make everything prettier. The core WCF team made the change to the ServiceContractGenerator code, svcutil / ASR now generates both synchronous and task-based asynchronous calls (and on Win8 we remove the synchronous ones to force people to do the right thing), all client code can call the asynchronous methods on the proxies, using the await keyword, and write beautiful applications which will make the world a happier place.
Oh, yeah, that thingy… That’s the issue in this whole discussion. Let’s review the contract which I used (or for those who jumped straight to this item):
When we generate the client in the new Task world, the operations of the “normal” / “forward” / “client-to-server” contract (IOrderSystem) will have tasks. Nobody had any disagreement on this topic when this feature was being designed. The client controls what operation it can call, so it just have the Task-based operation, and it can call it either using the new magic await keyword, or it can deal with the tasks directly.
The callback contract, on the other hand, does not have operations with tasks – we wanted to make the client implementation simple, and in the large majority of the cases it does not have to worry about tasks – using task-based operations is fairly easy (with await), but implementing “services” in a correct way isn’t as much (see Brad Wilson’s series about TPL and services for more information).
But we can still do better. Just like we did for Silverlight, we decided to provide the event-based callback support for Metro-style applications as well (the subset of WCF supported in that platform includes the TCP transport, which supports duplex services). That means that if your callback contract has a non-one-way operation, a warning will be emitted in the tool. The scenario is still supported (you’d need to handcraft the callback contract on the client), but we’re optimizing the tooling aspect for the 99% usage scenarios.
Well, that’s it. This post was a little different that nothing really new was presented, this was an internal discussion which I think was interesting for why we decided to make that decision on the new tooling.