In many cases a client calls a service and waits for the service call to complete forming a “one way” communication channel from the client to the service. But what if you wanted a two way communication channel, for example the client calls a service method and the service then calls methods on all connected clients. This two way pattern is known as duplex.
Create the service
Let’s create a very simple duplex service which will send messages to the connected clients when changes occur on the service.
1. Create a new console project
2. Add a new item, select WCF Service and name it whatever you want (mine’s called MyService)
3. In the Program.cs Main method type the following
ServiceHost serviceHost = new ServiceHost(typeof(MyService));
serviceHost.Open();
Console.WriteLine("Service started, press any key to quit");
Console.ReadKey();
serviceHost.Close();
4. Add a new interface named IMyCallback
public interface IMyCallback
{
[OperationContract(IsOneWay = true)]
void Tick(DateTime dateTime);
}
5. On the IMyService contract alter the ServiceContract to read
[ServiceContract(CallbackContract = typeof(IMyCallback))]
6. Rename the default method DoWork() to Register(), in both the interface and implementation.
7. Change the Register implementation to the following
private readonly List<IMyCallback> callbacks = new List<IMyCallback>();
public void Register()
{
callbacks.Add(OperationContext.Current.GetCallbackChannel<IMyCallback>());
}
8. For this demo we’re going to simply call the connected client(s) periodically so add the following to MyService
private readonly Timer timer;
public MyService()
{
timer = new Timer(TimerCallback);
timer.Change(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));
}
private void TimerCallback(object o)
{
foreach (IMyCallback callback in callbacks)
{
callback.Tick(DateTime.Now);
}
}
9. Finally edit the app.config. We’re going to use the TCP binding so set the httpGetEnabled to false, change the service binding to netTcpBinding and the mex binding to mexTcpBinding and alter the baseAddress to use the net.tcp protocol (see below)
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="DuplexService.MyService">
<endpoint address="" binding="netTcpBinding" bindingConfiguration=""
contract="DuplexService.IMyService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" bindingConfiguration=""
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8732/MyService/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
Create the client
1. Create a new console application
2. Right mouse click on References and choose Add Service Reference
3. Enter t baseAddress from the server (i.e. net.tcp://localhost:8732/MyService) then press Go
4. Set the namespace to MyService and press OK
5. We now need to implement the callback interface
public class ClientCallback : IMyServiceCallback
{
public void Tick(DateTime dateTime)
{
Console.WriteLine(dateTime);
}
}
6. And finally add the code to connect to the service
InstanceContext callback = new InstanceContext(new ClientCallback());
MyServiceClient client = new MyServiceClient(callback);
client.Open();
client.Register();
Console.WriteLine("Press a key to exit");
Console.ReadKey();
client.Close();
Now if you run the server and then one or more clients, after 10 seconds you should see the clients updated every ten seconds. This is fine except that each time a client connected to the service a new service was created for each new client. If we wanted a single instance of the server to be connected to multiple clients we can simply change a few lines in the service code.
1. On the MyService implementation add the following attribute
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
2. In the Program.cs Main method make the following two changes
MyService service = new MyService();
ServiceHost serviceHost = new ServiceHost(service);
With the above we create a single instance of the MyService then assign it the the ServiceHost.