The no frills, bare-bones example to Duplex WCF

30.01.2008 - 15:51 (6 years, 2 months, 2 weeks, 1 day ago)

download, dotnet, 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)