devxlogo

Eliminate Server Polling with WCF Callbacks

Eliminate Server Polling with WCF Callbacks

he traditional ASMX web service request/response communication model is limiting, because it is passive?in other words, web services return results only when called by client code. For example, imagine a scenario in which a particular cinema operator deploys a web service so users can purchase tickets online. The cinema’s branches have systems that connect to the web service to obtain the current seat allocation and that sell tickets to cinema-goers. Using this model, the branch systems have to poll the web service regularly to get updated seat information. The model also makes it very likely that more than one branch system will attempt to book the same seat(s) at the same time.

A better approach would be for the web service to notify all the branches whenever seat status changes and a seat has been reserved. That way, all the branches would have the latest seat information, and remove the need to poll the web service, reducing the overall load. However, to accomplish this, you would need a communication model where the clients remain connected to the service, so they can be notified when an event occurs. Using WCF, you can implement this communication model using callbacks.

A callback allows a service to call back its clients. The roles of the service and the client are now reversed?the client becomes a service and the service becomes a client.

Build a WCF Ticketing Service
In this section you’ll see how to build a WCF ticketing service that allows clients to book tickets. When multiple clients are connected to the service, the system will broadcast seats booked by any client to all the connected clients. Figure 1 shows the system flow.

?
Figure 1. System Flow: In this system, seats booked by any client are broadcast to all other connected clients.
?
Figure 2. WCF Ticketing Web Service: Create a new WCF Service Library project and name it WcfTicketingService.

First, you need to create the WCF service that allows clients to book cinema tickets. Launch Visual Studio 2008 and create a new WCF Service Library project. Name it WcfTicketingService (see Figure 2).

In this example, the WCF service will be hosted by the WCF Service Host, a utility provided by Visual Studio 2008. When you create the project, you’ll see an IService1.cs file. Open that file and define the following service and data contracts (note that you can download the code for this project):

   using System;   using System.Collections.Generic;   using System.Linq;   using System.Runtime.Serialization;   using System.ServiceModel;   using System.Text;      namespace WcfTicketingService   {      [ServiceContract(         Name = "TicketingService,"         Namespace = "http://www.learn2develop.net/,"         CallbackContract = typeof(ITicketCallBack),         SessionMode = SessionMode.Required)]      public interface ITicketService      {         [OperationContract(IsOneWay = true)]         void SetSeatStatus(string strSeats);            [OperationContract(IsOneWay = true)]         void RegisterClient(Guid id);            [OperationContract(IsOneWay = true)]         void UnRegisterClient(Guid id);      }         public interface ITicketCallBack      {         [OperationContract(IsOneWay = true)]         void SeatStatus(string message);      }         //---each client connected to the service has a GUID---      [DataContract]      public class Client      {         [DataMember]         public Guid id { get; set; }      }   }

The ITicketService interface defines three operations:

  • SetSeatStatus: Allows clients to book seats. This operation accepts a string value containing the seats to be booked.
  • RegisterClient: Registers a client when it connects to the service. This operation accepts a GUID that uniquely identifies each client.
  • UnRegisterClient: Unregisters a client when it disconnects from the service. This operation accepts the client GUID.

Note that The ITicketService interface is decorated with the [ServiceContract] attribute. The CallbackContract property specifies the interface that defines the callback operation. In the preceding code, the SessionMode property is set to Required, indicating that state must be maintained between the service and client.

The other interface defined in the preceding code, the ITicketCallBack interface, contains only one operation?SeatStatus, which allows the service to initiate a callback to the client. The callback updates the client, letting it know which seats have been booked by other clients.

Finally, the Client class defines the data contract. Its single member holds the GUID of a client connecting to the service.

One-Way vs. Two-Way Operations
All the operations defined in the two interfaces are one-way operations (marked IsOneWay = true). To understand why, assume that all the operations use the default request/response model. When a client calls the SetSeatStatus() method to book seats, it will wait for a response from the service. However, the service will now invoke the SeatStatus callback on the client (the service informs all clients about the seats booked) and wait for a reply from the client. However, that would cause a deadlock, because the client would be waiting for a response from the service while the service would be waiting for a response from the client after invoking the callback. By defining the operations as one-way, the service can invoke the callback on the client without waiting for a reply from the client, thus eliminating the possibility of a deadlock.

Define the Classes
In the Service1.cs file, define the SeatStatus class:

   using System;   ...   using System.Text;   using System.Timers;      namespace WcfTicketingService   {      //...   }      public class SeatStatus   {      //---a string representing the seats booked by a client---      public string Seats { get; set; }   }

The SeatStatus class contains a property called Seats that stores the seats booked by a client.

Also in the Service1.cs file, define the Ticketing class, which implements the ITicketingService service contract (see Listing 1).

The Ticketing class contains the implementations for the three methods defined in the ITicketService interface:

  • RegisterClient(): Clients call this method when connecting to the service for the first time. The service stores the list of connected clients in a generic Dictionary object, using the GUID for the key, and it’s the client’s callback handler as the value.
  • UnRegisterClient(): Clients call this method when disconnecting from the service, which removes the calling client’s entry from the Dictionary object.
  • SetSeatStatus(): Clients call this method to book seats. The seats to be booked are stored in a SeatStatus object. When seats are booked, the Ticketing class creates an Action delegate that invokes the callback of each client, passing the newly-booked seats.

Near the top of the code in Listing 1, the [ServiceBehavior] attribute specifies the InstanceContextMode property as Single and the ConcurrencyMode property as Multiple. When a WCF Service receives a message, the message gets dispatched to an object’s instance methods. The options are:

  • A single instance of the receiver may be created for all clients
  • A single instance of the receiver may be created for each client

The InstanceContextMode property specifies the number of service instances available for handling calls contained in incoming messages. It can be one of the following:

  • Single: Every received message gets dispatched to the same object (a singleton).
  • Percall (default): Every received message is dispatched to a newly created object.
  • PerSession: Messages received within a session (usually a single sender) are dispatched to the same object.
  • Shareable: Messages received within a session (can be one or more senders) are dispatched to the same object.

You can also set how threads are used to manage all received messages, using either one thread at a time to access the receiver object(s), or using multiple threads to access the receiver object(s) concurrently.

The ConcurrencyMode property of the [ServiceBehavior] attribute controls how you handle all incoming messages. The property can assume of the following values:

  • Single (default): Only one thread may access the receiver object at a time
  • Multiple: Multiple threads may access the receiver object concurrently
  • Reentrant: Only one thread may access the receiver object at a time, but callbacks may re-enter that object on another thread

When you use the Multiple mode on the service, you need to take special care to make sure that threads are synchronized properly and that critical regions are locked during thread access.

To keep the demonstration simple, the sample project was developed using the following assumptions:

  • In real life, the application should save seats booked by a client in database or array; however for this example, they are simply broadcast to all connected clients.
  • When new clients connect to the server, the current seat allocation status (for instance, which seats are booked, which are not, etc.) should be sent to the client.

Now, double-click on the App.config file in Solution Explorer. Change the attribute values to match the following:

                                                                                             ...

Right-click on the App.config file and select Edit WCF Configuration. Expand the EndPoints node (see Figure 3) and select the first [Empty Name] node.

?
Figure 3. Endpoint Properties: Visual Studio provides UI for setting the endpoint properties stored in App.config.

?
Figure 4. WCF Test Client: Although the WCF Test Client shows errors, that’s normal for this application.

Set the endpoint properties as shown in Table 1.

Table 1. Endpoint Properties: Set the endpoint properties as shown in the table and illustrated in Figure 3.
PropertyValue
Addressnet.tcp://localhost:5000/TicketingService
BindingnetTcpBinding

You will use TCP as the transport protocol.

Save the App.config file and close the configuration window. Press F5 to run and debug the service. In the WCF Test Client, you will see something like Figure 4. The error message is normal.

Building the Client
With the WCF service completed, you can now build the client that consumes the service. Using the same solution, add a new Windows Forms Application project, named Client.

Add a service reference to the WCF ticketing service (in the Add Service Reference dialog, click the Discover button and you should be able to discover the Ticketing WCF service (see Figure 5). Select it, and then click OK.

?
Figure 5. Add Service Reference: Using the Add Service Reference dialog, add a reference to the WCF ticketing service.
?
Figure 6. Sample Controls: Populate Form1 with the controls shown in this figure.

Next, populate Form1 with the controls shown in Figure 6. Set the form’s Size property to 477, 387.

In the code-behind for Form1, import the namespace shown below, and then declare the following constants and objects:

   using System.ServiceModel;   namespace Client   {      public partial class Form1 : Form      {         int ROWS = 10;         int COLUMNS = 10;         const int SEAT_WIDTH = 45;         const int SEAT_HEIGHT = 25;         const int START_X = 10;         const int START_Y = 40;            static Button[,] seatsArray;            private ServiceReference1.TicketingServiceClient _client;         private Guid _guid = Guid.NewGuid();

Define the SeatsOccupied() static function within the Form1 class as follows:

      public partial class Form1 : Form      {         ...         ...         ...         //---set all occupied seats in red---         public static void SeatsOccupied(string strSeatsOccupied)         {            string[] seats = strSeatsOccupied.Split(',');            for (int i = 0; i < seats.Length - 1; i++)            {               string[] xy = seats[i].Split('-');               Button btn = seatsArray[int.Parse(xy[0]) - 1,                   int.Parse(xy[1]) - 1];               btn.BackColor = Color.Red;            }         }      }

The SeatsOccupied function in the preceding code accepts a string containing the seats that are occupied. You'll see more about that in a minute. The seats themselves will be represented by button controls. Whenever a user books a seat (using the Button control), the application changes that seat's background color to red (meaning it's reserved).

Define the SeatStatusCallback class and implement the SeatStatus() method as defined in the TicketingServiceCallback interface (defined in the service):

   namespace Client   {      public partial class Form1 : Form      {         //...      }         public class SeatStatusCallback :          ServiceReference1.TicketingServiceCallback      {         public void SeatStatus(string message)         {            Form1.SeatsOccupied(message);         }      }   }

The client invokes the SeatStatus() method when the web service calls the client's callback. In this case, you call the static SeatsOccupied() function to update the seats status.

Code the Form1_Load event handler as follows:

   private void Form1_Load(object sender, EventArgs e)   {      InstanceContext context =         new InstanceContext(new SeatStatusCallback());      _client = new          ServiceReference1.TicketingServiceClient(context);      _client.RegisterClient(_guid);         //---display the seats---      seatsArray = new Button[COLUMNS, ROWS];      for (int r = 0; r < ROWS; r++)      {         for (int c = 0; c < ROWS; c++)         {            Button btn = new Button();            btn.Location = new Point(               START_X + (SEAT_WIDTH * c),               START_Y + (SEAT_HEIGHT * r));               btn.Size = new Size(SEAT_WIDTH, SEAT_HEIGHT);            btn.Text = (c + 1).ToString() + "-" +               (r + 1).ToString();            btn.BackColor = Color.White;            seatsArray[c, r] = btn;            btn.Click += new EventHandler(btn_Click);            this.Controls.Add(btn);         }      }   }

The preceding code basically creates an instance of the InstanceContext class by passing it an instance of the SeatStatusCallback class. It then creates an instance of the WCF client using the constructor (which requires an InstanceContext object). In addition, it dynamically populates the form with a 10x10 array of Button controls that represent the seats, and wires each Button control's Click event to the btn_Click event handler, shown below:

   void btn_Click(object sender, EventArgs e)   {      if (((Button)sender).BackColor == Color.White)      {         ((Button)sender).BackColor = Color.Yellow;      }      else if (((Button)sender).BackColor == Color.Yellow)      {         ((Button)sender).BackColor = Color.White;      }   }

This event handler toggles the color of the seats as users click on the Button controls. White indicates that the seat is available; yellow indicates that the seat has been selected for booking.

Code the Book Seats button as follows:

   private void btnBookSeats_Click(object sender, EventArgs e)   {      string seatsToBook = string.Empty;      for (int r = 0; r < ROWS; r++)      {         for (int c = 0; c < ROWS; c++)         {            if (seatsArray[c, r].BackColor == Color.Yellow)            {               seatsToBook += seatsArray[c, r].Text + ,"";            }         }      }      //---send to WCF service---      _client.SetSeatStatus(seatsToBook);   }

To specify the seats that are selected for booking, create the string containing the seats to be booked in the following format:

   -,-,…

Finally, code the Form1_FormClosing event so it calls the web service to unregister the client, as follows:

   private void Form1_FormClosing(object sender, FormClosingEventArgs e)   {      _client.UnRegisterClient(_guid);   }

Testing the Application
To test the application, press F5 to debug and launch the service. When it's running, you can debug the client. Right-click on the Client project in Solution Explorer, and select Debug ? Start new instance. (See Figure 7).

?
Figure 7. Launching the Client: To debug an instance of the client, right-click on the client project in Solution Explorer, and select Debug ( Start new instance.
?
Figure 8. Real-Time Updates: When one client books seats, all the clients get updated in near real-time.

Run a few instances of the client and you can then start to book cinema tickets. Notice that as one client books the seats, the service updates the other clients automatically (see Figure 8).

That completes the example. You've seen how you to use WCF callbacks to implement a cinema ticketing system, and you have an example you can extend to meet your own needs. Callbacks are advantageous because they allow asynchronous communications between a client and a service, and eliminate the need to constantly poll the service.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist