The no frills, bare-bones example to Duplex WCF

30.01.2008 - 15:51 (2 years, 7 months, 2 days ago)

Filed under download, .NET, TrivadisContent, WCF

Can a WCF client call a WCF service and have the server call back with whatever? Oh, yes. Can you get an example how it's done? Indeed.

Are these good examples? Well, you can get MS samples for WCF (which in their entirety are really helpful) or another blog post on the subject. Don't get me wrong, they work, but in my humble opinion they have shortcomings.

  • They have funny classes and interfaces generated by svcutil with some really horrid artefacts
  • The interfaces are not shared among client & server which annoys my desire for harmony and DRYness, which should be followed even in an example (or, maybe, especially in an example).

Anyway, here's my version, which is quite reduced to just the duplex stuff.

Common bits

[ServiceContract(Namespace = "rf.services", 
   CallbackContract = typeof(IDataOutputCallback), 
   SessionMode = SessionMode.Required)]
  public interface IServerWithCallback
  {
      [OperationContract(IsOneWay=true)]
      void StartDataOutput();
  }

That's the Server interface which the client will use to talk to the server. Please note the CallBackContract property of the ServiceContract attribute. That's the interface that will have to be implemented by the client. The server will use it to call back.

public interface IDataOutputCallback { [OperationContract(IsOneWay = true)] void SendDataPacket(string data); }

All this IsOneWay business is one way to avoid issues when you're calling back a client within the method that is being called by the client. The issues are described here.

Server

Of course we need an implementation of the service (doh):

class ServerWCallbackImpl : IServerWithCallback { 
  #region IServerWithCallback Members 
  public void StartDataOutput() { 
    IDataOutputCallback callback = OperationContext.Current.GetCallbackChannel<IDataOutputCallback>(); 
    for (int i = 0; i < 10; i++) { 
      Random r = new Random(); 
      int interval = r.Next(500,3000); 
      System.Threading.Thread.Sleep(interval); 
      callback.SendDataPacket("Packet " + i.ToString()); 
    } 
    callback.SendDataPacket("Last packet is this one :)"); 
  }
  #endregion 
}
The important line is the very first one. The rest is just a crappy example to get some funny callback side-effects. Next we have the configuration of the thing...

<system.serviceModel>
    <services>
      <service name="ServerPart.ServerWCallbackImpl">
        <endpoint address="net.tcp://localhost:9080/DataService" binding="netTcpBinding"
            bindingConfiguration="" name="DataEndpoint" contract="CommonParts.IServerWithCallback" />
      </service>
    </services>
</system.serviceModel>

And some boilerplate to get it running e.g. in a command-line:

ServiceHost svc = new ServiceHost(typeof(ServerWCallbackImpl)); 
svc.Open(); 
Console.WriteLine("Listening according to configuration"); 
Console.ReadKey(); 

If you want to save yourself the configuration etry in the app.config file, you can also set up the ServiceHost programmatically:

  ServiceHost duplex = new ServiceHost(typeof(ServerWCallbackImpl)); 
  duplex.AddServiceEndpoint(typeof(IServerWithCallback), new NetTcpBinding(), "net.tcp://localhost:9080/DataService"); 
  duplex.Open(); 

Client

Want to call the server? Well, first you could go and configure it:

<system.serviceModel>
    <client>
      <endpoint address="net.tcp://localhost:9080/DataService" binding="netTcpBinding"
            bindingConfiguration="" contract="CommonParts.IServerWithCallback" name="Callback">
      </endpoint>
    </client>
</system.serviceModel>

Then it'll make sense to provide an implementation of the Callback interface:

class CallbackImpl : IDataOutputCallback { 
  #region IDataOutputCallback Members 
  public void SendDataPacket(string data) { 
    Console.WriteLine(data); } #endregion 
  } 

Brill! Finally the boilerplate to kickstart the thing:

DuplexChannelFactory<IServerWithCallback> cf = new DuplexChannelFactory<IServerWithCallback>( new CallbackImpl(), "Callback"); 
IServerWithCallback srv = cf.CreateChannel(); 
srv.StartDataOutput(); 

Again, if you prefer working without a configuration entry, you can set this up programmatically, too:

DuplexChannelFactory<IServerWithCallback> cf = new DuplexChannelFactory<IServerWithCallback>(
  new CallbackImpl(), new NetTcpBinding(), new EndpointAddress("net.tcp://localhost:9080/DataService")); 

That's it, you shouldn't need more, honest, dude. No generated class in sight, all interfaces are the same throughout Client and Server. Distribute over projects/threads/app domains at your leisure and enjoy.

Update: There is a small vs2008 solution attached below that shows the programmatic setup of this blog post.

Attachments

RF.WCF.Callback.zip (30.37 KB)

Comments

WCFNewbie ( 18.02.2008 - 10:47 UTC )
Very Nice idea. But is it just me or? Namespace rf.services has to be the same throughout the solution? service name="ServerPart.ServerWCallbackImpl" the ServerPart bit and contract="CommonParts.IServerWithCallback" the CommonParts bit These name bits is named by you, how can a Newbie make this into a working VS solution? Kind regards
The local Borg outlet ( 18.02.2008 - 13:43 UTC )

WCF Xample - where goes what

Guys, why can't you use a name when you comment? Anyway...

I Can't provide a downloadable solution right now, as the one I used is currently polluted, but I shall clean it up again

The chosen service name is a default when you do not provide anything else - It is the fully qualifiedname of the type that implements the contract. All contracts are defined in their own project that is shared between client and server. All contract="..." entries refer to the fully qualified names of the interfaces.

The name namespace in the attribute is in fact unnecessary in this example, as it refers to WSDL specifics and has nothing to do with .NET namespaces

The name Callback of the client endpoint is used again when creating the DuplexChannelFactory and those two must be equal. Hope this helps along a bit .

Kaitain ( 19.03.2008 - 14:00 UTC )
Thanks for your example. One complaint I have is all the configuration being in the app.config files. That is HORRIBLE teaching practice because the student has no idea how these end points are created. Instead of the inflexible App.config files please always create everything in code so someone following it can understand what's going on. I know Microsoft does this but I think we can all agree MS examples are pretty bad when it comes to being standards based (e.g. do you know anyone that uses My as a prefix in their production code?)
Quedi ( 03.04.2008 - 08:31 UTC )
I kind of like externalizing the configuration of WCF, either way, I have included the code lines to get the stuff running without a config file whatsoever.
Khaled ( 17.05.2008 - 09:19 UTC )
Have you got a working example yet? I am really keen to look at it as it seems far simpler and far more reusable than Microsoft examples. Please post if you have one. Regards Khaled
Jon Fuller ( 04.02.2009 - 13:50 UTC )
Excellent, super-slim starter for duplexing in WCF. This is EXACTLY what I was looking for, no "crufty" examples from MS. Just lean code that works. Again, thanks!
stephen strother ( 25.02.2009 - 12:16 UTC )
The was exactly what I wanted. I have a one way contract and wanted to know (and understand) how to include a callback. Short and simple. Perfect
CP ( 28.07.2009 - 11:04 UTC )
You're wrong.
Code Tarzan ( 13.11.2009 - 22:07 UTC )
This is exactly what I needed. Many many thanks..no google ads to click on to show thanks.
wow ( 15.01.2010 - 15:36 UTC )
Sorry Kaitain, really wrong
C Sharp ( 14.03.2010 - 16:11 UTC )

Nice example... DuplexChannelFactory is a Generic type 'DuplexChannelFactory ()' so your example will not complie.

DuplexChannelFactory cf = new DuplexChannelFactory( new CallbackImpl(), "Callback");

Le grandseigneur du le Site ( 15.03.2010 - 08:28 UTC )

It sure is...the formatting got messed up, should look fine again.

Kevin ( 15.04.2010 - 22:39 UTC )

Thanks for the example. Much easier to understand exactly what's happening. I appreciate the 100% programmatic approach too.

Liam ( 11.05.2010 - 13:31 UTC )

Great example - just what I was looking for - not a designer in sight!

Mako ( 26.05.2010 - 05:18 UTC )

FINALLY! No more stuffing around figuring out why I can't "Add Service Reference" and the endless config options. Just clean, simple CODE. Thank you!

JesusFreak ( 11.06.2010 - 16:26 UTC )

This was very helpful -- exactly what I was looking for. Thank you!

ynoT ( 24.08.2010 - 14:10 UTC )

will this work against a javascript client script callback? (ajax)

Quedi ( 24.08.2010 - 17:26 UTC )

ynoT, I doubt it, since the technological foundation is probably too disparate, but I am not sure. To get that sort of connectivity you should have a look at Comet

Post a comment