Share via


Message Patterns in WCF Services

Robert Green

MCW Technologies

Download

Articles in this series

Introduction

When client applications and WCF services communicate, they do so by passing XML messages. WCF supports three message exchange patterns that describe how clients and services pass messages. These are request-reply, one-way (also called datagram), and duplex (also called callback).

In the request-reply pattern, a client application sends a message to a WCF service and then waits for a reply. This is the classic pattern for any service, including both WCF and Web services. You will use this pattern anytime a client application requests data from a service. For example, a client calls a method of a service, such as GetStockPrice, GetCustomers or CalculatePayment. The service returns the requested information. The client has asked a question and because it expects an answer, the client waits for the service to send back a message containing the answer.

You will also use this pattern when a client makes a request of a service to perform an action and the client needs some confirmation. For example, the client calls the SaveCustomerData or UpdatePrice method of the service. The client waits for the service to send a response indicating whether the service successfully completed the request.

You will use the request/reply pattern more often than the other patterns. Most of the examples you have seen in this tutorial series use this pattern. In this tutorial, you will explore the one-way and duplex message exchange patterns and see how to configure your services and clients to support them.

Use the One-Way Message Exchange Pattern

In the one-way message exchange pattern, a client application sends a message to a WCF service but the service does not send a reply message to the client. You can use this pattern when a client requests the service take an action but does not need to wait for a reply. Some one-way pattern scenarios include the following:

  • Logging in and out. You want to record when users start and exit the client application. If you record this locally, you would use code in the client. If you record this information centrally, you could add a LogIn and LogOut method to the service. When the user starts the application, the client calls the LogIn method, passing the user’s name and day/time. When the user exits the application, the client calls the LogOut method, passing the same information. The user does not need to wait for either of these calls to return a value, so they can be one-way operations.
  • Maintenance tasks. You write a service that supports placing an order. The service retrieves customer data on the user and stores that in a database table on the server. The user adds items to a shopping cart and these items are stored in a temporary table as well. When the user places or cancels an order, the client application calls the CleanUp method of the service, which then deletes the temporary tables. This method can be one-way.
  • Heads down data entry and other repetitive tasks. Consider a scenario where a data entry person takes a sheet of paper off a pile, types the information into a data entry form, clicks the Save button and then moves on to the next sheet of paper. When the user clicks the button, the client application calls the SaveData method of a WCF service to write the information to a database. Pausing the application, even for a second, while the client application waits for a response from the service, slows down data entry and causes the clerk to enter less data over the course of each day. If you configure the SaveData method to be one-way, the data entry clerk can enter data as fast as he or she can type and click a button.

To explore the one-way message exchange pattern, start Visual Studio 2008 and select File | Open | Project/Solution to display the Open Project dialog box. Navigate to the folder where you downloaded this tutorial’s sample project. Select OneWayDemo.sln and click OK to open the project. The sample application includes three projects. The OneWayDemoServiceLibrary project represents a WCF service that clients can call to buy and sell stocks.

The BuyStock and SellStock methods of the service take as a parameter a stock symbol and a quantity. The IsOneWay property of the OperationContract attribute is set to true for both of these methods. The client can call each method and not wait for it to complete before making the next call to the service.

[OperationContract(IsOneWay = true)]

void BuyStock(string stock, int quantity);

[OperationContract(IsOneWay = true)]

void SellStock(string stock, int quantity);

<OperationContract(IsOneWay:=True)> _

Sub BuyStock(ByVal stock As String, ByVal quantity As Integer)

<OperationContract(IsOneWay:=True)> _

Sub SellStock(ByVal stock As String, ByVal quantity As Integer)

The BuyStock and SellStock methods validate the stock symbol and then record the transaction results in a text file. For the purposes of this demo, valid stock symbols are contained in a string.

private string validStocks = "MSFT,GOOG,F,AAPL,ORCL,GE";

public void BuyStock(string stock, int quantity)

{

  // Create the OneWayDemo folder if necessary

  Directory.CreateDirectory("C:\\MessagePatternDemos");

  // Record the stock transaction

  writeString = new StringWriter();

  if (ValidateStock(stock))

  {

    writeString.WriteLine(

      "{0} shares of {1} were purchased on {2} at {3}",

      quantity, stock, DateTime.Today.ToShortDateString(),

      DateTime.Now.ToShortTimeString());

  }

  else

  {

    writeString.WriteLine(

      "Attempt to purchase {0} shares of {1} failed on {2} at {3}",

      quantity, stock, DateTime.Today.ToShortDateString(),

      DateTime.Now.ToShortTimeString());

  }

  File.AppendAllText("C:\\MessagePatternDemos\\Transactions.txt",

    writeString.ToString());

}

private bool ValidateStock(string stock)

{

  return (validStocks.IndexOf(stock) != -1);

}

Private validStocks As String = "MSFT,GOOG,F,AAPL,ORCL,GE"

Public Sub BuyStock(ByVal stock As String, ByVal quantity As Integer) _

  Implements IOneWayDemoService.BuyStock

  ' Create the OneWayDemo folder if necessary

  Directory.CreateDirectory("C:\MessagePatternDemos")

  ' Record the stock transaction

  writeString = New StringWriter()

  If ValidateStock(stock) Then

    writeString.WriteLine( _

      "{0} shares of {1} were purchased on {2} at {3}", _

      quantity, stock, DateTime.Today.ToShortDateString(), _

      DateTime.Now.ToShortTimeString())

  Else

    writeString.WriteLine( _

      "Attempt to buy {0} shares of {1} failed on {2} at {3}", _

      quantity, stock, DateTime.Today.ToShortDateString(), _

      DateTime.Now.ToShortTimeString())

  End If

  File.AppendAllText("C:\MessagePatternDemos\Transactions.txt", _

    writeString.ToString())

End Sub

Private Function ValidateStock(ByVal stock As String) As Boolean

  Return validStocks.IndexOf(stock) <> -1

End Function

Press F5 to run the application. In the form, click Buy and sell stocks. The form calls the BuyStock method three times to buy three different stocks. It then calls the SellStock method three times to sell three different stocks. The form then displays a message box informing you it is finished. Click OK to dismiss the message box and close the form.

In the Windows Explorer, navigate to the C:\MessagePatternDemos folder and open the Transactions.txt file. The file should contain text like the following:

100 shares of MSFT were purchased on 6/3/2009 at 2:06 PM

50 shares of GOOG were purchased on 6/3/2009 at 2:06 PM

Attempt to purchase 500 shares of FMC failed on 6/3/2009 at 2:06 PM

100 shares of AAPL were sold on 6/3/2009 at 2:06 PM

300 shares of ORCL were sold on 6/3/2009 at 2:06 PM

Attempt to sell 75 shares of GMC failed on 6/3/2009 at 2:06 PM

Explore the Implications of Using One-Way Operations

To configure a method to use the one-way message exchange pattern, you simply set the IsOneWay property of the method’s OperationContract attribute to true. You can then call the method from client applications and not wait for a response.

Before moving on to the duplex pattern, you should understand an important implication of using the one-way pattern. Because the service does not send a reply message to the client, the service will not inform the client if the method fails.

In the code you just ran, you successfully purchased the stocks with the symbols MSFT and GOOF and sold the stocks with the symbols AAPL and ORCL. However, your attempt to purchase the stock with the symbol FMC and to sell the stock with the symbol GMC failed. The service dutifully recorded this, but did not notify the client application.

If it is important that the application notify users when a service request does not succeed due to invalid data, then you shouldn’t use the one-way pattern.

Another implication of using the one-way pattern is that the service does not notify the client if a method call fails due to an exception. To see this, you will add code to the service to thrown an exception if the client application specifies an invalid stock symbol. In the Solution Explorer, double click the OneWayDemoService file in the OneWayDemoServiceLibrary project. Make the following changes to the SellStock method:

if (ValidateStock(stock))

{

  writeString.WriteLine(

    "{0} shares of {1} were sold on {2} at {3}",

    quantity, stock, DateTime.Today.ToShortDateString(),

    DateTime.Now.ToShortTimeString());

  File.AppendAllText("C:\\MessagePatternDemos\\Transactions.txt",

    writeString.ToString());

}

else

{

  writeString.WriteLine(

    "Attempt to sell {0} shares of {1} failed on {2} at {3}",

    quantity, stock, DateTime.Today.ToShortDateString(),

    DateTime.Now.ToShortTimeString());

  File.AppendAllText("C:\\MessagePatternDemos\\Transactions.txt",

    writeString.ToString());

  throw new FaultException(string.Format(

    "{0} is an invalid stock symbol", stock));

}

If ValidateStock(stock) Then

  writeString.WriteLine( _

    "{0} shares of {1} were sold on {2} at {3}", _

    quantity, stock, DateTime.Today.ToShortDateString(), _

    DateTime.Now.ToShortTimeString())

  File.AppendAllText("C:\MessagePatternDemos\Transactions.txt", _

    writeString.ToString())

Else

  writeString.WriteLine( _

    "Attempt to sell {0} shares of {1} failed on {2} at {3}", _

    quantity, stock, DateTime.Today.ToShortDateString(), _

    DateTime.Now.ToShortTimeString())

  File.AppendAllText("C:\MessagePatternDemos\Transactions.txt", _

    writeString.ToString())

  Throw New FaultException(String.Format( _

    "{0} is an invalid stock symbol", stock))

End If

If the client specifies an invalid stock symbol, the method first records that in the text file and then throws an exception.

Press CTRL+F5 to build and run the application. In the form, click Buy and sell stocks. The form calls the BuyStock method three times, calls the SellStock method three times and then displays a message box informing you it is finished. Click OK to dismiss the message box and close the form.

In the Windows Explorer, navigate to the C:\MessagePatternDemos folder and open the Transactions.txt file. At the bottom of the file, you should see the same text you saw before with a later time specified. However, the client is unaware that the service threw an exception.

If you use the request/reply pattern and the service throws an exception, the service will return a message to the client containing the fault information. If you use the one-way pattern, the service does not return a message to the client and therefore the client is not aware that a fault occurred.

If it is important that the application notify users when a service request does not succeed due to an exception, then you shouldn’t use the one-way pattern.

Use the Duplex or Callback Message Exchange Pattern

In the request/reply and one-way message exchange patterns, only the client can initiate communication. In the duplex pattern, both the client and the service can initiate communication. The client calls a method of the service. The service can then use a client callback to call a method in the client. You can use this pattern when you want the service to send a notification or alert to the client after the client has called the service.

In the example you just ran, the client application calls the BuyStock or SellStock methods of the service and does not wait for a response. The one-way pattern is the appropriate pattern to use in that scenario. If you want the application to confirm the results of the purchase or sale, you could switch to the request/reply pattern and have the service return a message to the client application.

If the service executes the code to buy or sell a stock in a short period, then waiting for a response is acceptable. Suppose it takes five or ten seconds for the stock transaction to occur. You do not want to freeze the client application for that long while waiting for the service to reply. This is a good scenario for the duplex pattern. The client calls the BuyStock or SellStock method of the service and then continues. When the service is finished running the method, it will call back into the client, which can then display a confirmation message to the user.

To explore the duplex message exchange pattern, select File | Open | Project/Solution to display the Open Project dialog box. Navigate to the folder where you downloaded this tutorial’s sample project. Select DuplexDemo.sln and click OK to open the project. The sample application is based on the completed version of the OneWayDemo application. There are two main differences: You buy or sell stocks one at a time and the service does not throw an exception if the client passes an invalid stock symbol.

Press F5 to run the application. In the form, enter MSFT in the stock text box and 100 in the shares text box. Then click Buy. Next, enter AAPL in the stock text box and 100 in the shares text box. Then click Sell. Close the form.

You will now modify this application to use duplex communication. The first step you will take is to add a callback contract to the service. In the Solution Explorer, double-click the IDuplexDemoService file. Add the following code to define the callback interface:

public interface IDuplexDemoServiceCallback

{

  [OperationContract(IsOneWay = true)]

  void BuyStockCallBack(string message);

  [OperationContract(IsOneWay = true)]

  void SellStockCallBack(string message);

}

Public Interface IDuplexDemoServiceCallback

  <OperationContract(IsOneWay:=True)> _

  Sub BuyStockCallBack(ByVal message As String)

  <OperationContract(IsOneWay:=True)> _

  Sub SellStockCallBack(ByVal message As String)

End Interface

Next, you will use the CallBackContract property of the ServiceContract attribute to associate the callback contract with the service contract. To do this, make the following change:

[ServiceContract(

  CallbackContract=typeof(IDuplexDemoServiceCallback))]

public interface IDuplexDemoService

<ServiceContract( _

  CallbackContract:=GetType(IDuplexDemoServiceCallback))> _

Public Interface IDuplexDemoService

Note that you are modifying the ServiceContract attribute on the IDuplexDemoService contract. Next, you will modify the BuyStock and SellStock methods so that they call back to the client when they are finished executing the stock transaction. In the Solution Explorer, double-click the DuplexDemoService file. Add the following code to the top of the BuyStock and SellStock methods:

IDuplexDemoServiceCallback callback =

  OperationContext.Current.GetCallbackChannel<

  IDuplexDemoServiceCallback>();

Dim callback As IDuplexDemoServiceCallback = _

  OperationContext.Current.GetCallbackChannel( _

  Of IDuplexDemoServiceCallback)()

To call back into the client, the service needs a reference to the client. To get this reference, you can use the OperationContext class. This provides access to the service’s operation context. The Current property returns the current context. The GetCallbackChannel method returns a reference to a communication channel from the service back to the client. This is a generic method, so you specify the callback interface as a type parameter.

Make the following changes to the BuyStock method:

writeString = new StringWriter();

string confirmation = string.Empty;

if (ValidateStock(stock))

{

  writeString.WriteLine(

    "{0} shares of {1} were purchased on {2} at {3}",

    quantity, stock, DateTime.Today.ToShortDateString(),

    DateTime.Now.ToShortTimeString());

  confirmation=string.Format(

    "{0} shares of {1} were purchased", quantity, stock);

}

else

{

  writeString.WriteLine(

    "Attempt to purchase {0} shares of {1} failed on {2} at {3}",

    quantity, stock, DateTime.Today.ToShortDateString(),

    DateTime.Now.ToShortTimeString());

  confirmation=string.Format(

    "Attempt to purchase {0} shares of {1} failed", quantity, stock);

}

File.AppendAllText("C:\\MessagePatternDemos\\Transactions.txt",

  writeString.ToString());

System.Threading.Thread.Sleep(new TimeSpan(0, 0, 5));

callback.BuyStockCallBack(confirmation);

writeString = New StringWriter()

Dim confirmation As String = String.Empty

If ValidateStock(stock) Then

  writeString.WriteLine( _

    "{0} shares of {1} were purchased on {2} at {3}", _

    quantity, stock, DateTime.Today.ToShortDateString(), _

    DateTime.Now.ToShortTimeString())

  confirmation = String.Format( _

    "{0} shares of {1} were purchased", quantity, stock)

Else

  writeString.WriteLine( _

    "Attempt to buy {0} shares of {1} failed on {2} at {3}", _

    quantity, stock, DateTime.Today.ToShortDateString(), _

    DateTime.Now.ToShortTimeString())

  confirmation = String.Format( _

    "Attempt to purchase {0} shares of {1} failed", quantity, stock)

End If

File.AppendAllText("C:\MessagePatternDemos\Transactions.txt", _

  writeString.ToString())

Threading.Thread.Sleep(New TimeSpan(0, 0, 5))

callback.BuyStockCallBack(confirmation)

This code waits five seconds to simulate the time it takes to buy a stock. The code then calls back to the client, passing a confirmation message.

Make the following changes to the SellStock method:

writeString = new StringWriter();

string confirmation = string.Empty;

if (ValidateStock(stock))

{

  writeString.WriteLine(

    "{0} shares of {1} were sold on {2} at {3}",

    quantity, stock, DateTime.Today.ToShortDateString(),

    DateTime.Now.ToShortTimeString());

  confirmation=string.Format(

    "{0} shares of {1} were sold", quantity, stock);

}

else

{

  writeString.WriteLine(

    "Attempt to sell {0} shares of {1} failed on {2} at {3}",

    quantity, stock, DateTime.Today.ToShortDateString(),

    DateTime.Now.ToShortTimeString());

  confirmation=string.Format(

    "Attempt to sell {0} shares of {1} failed", quantity, stock);

}

File.AppendAllText("C:\\MessagePatternDemos\\Transactions.txt",

  writeString.ToString());

System.Threading.Thread.Sleep(new TimeSpan(0, 0, 5));

callback.SellStockCallBack(confirmation);

writeString = New StringWriter()

Dim confirmation As String = String.Empty

If ValidateStock(stock) Then

  writeString.WriteLine( _

    "{0} shares of {1} were sold on {2} at {3}", _

    quantity, stock, DateTime.Today.ToShortDateString(), _

    DateTime.Now.ToShortTimeString())

  confirmation = String.Format( _

    "{0} shares of {1} were sold", quantity, stock)

Else

  writeString.WriteLine( _

    "Attempt to sell {0} shares of {1} failed on {2} at {3}", _

    quantity, stock, DateTime.Today.ToShortDateString(), _

    DateTime.Now.ToShortTimeString())

  confirmation = String.Format( _

    "Attempt to sell {0} shares of {1} failed", quantity, stock)

End If

File.AppendAllText("C:\MessagePatternDemos\Transactions.txt", _

  writeString.ToString())

Threading.Thread.Sleep(New TimeSpan(0, 0, 5))

callback.SellStockCallBack(confirmation)

The final thing you need to do to configure the service for duplex communication is change the binding. The service currently uses the WSHttpBinding binding, but that binding does not support duplex communication. You will use the WSDualHttpBinding binding instead. In the Solution Explorer, double click the Web.config file in the WebHost project. Scroll down to the services section and make the following change:

<endpoint address="" binding="wsDualHttpBinding"

          contract="DuplexDemoServiceLibrary.IDuplexDemoService">

Save your changes and build the solution.

You will next modify the client application to receive the callback communication from the service. First, you need to update the service reference to reflect the changes you made to the service contract. In the Solution Explorer, expand the Service References node in the WindowsClient project. Right-click on DuplexDemoService and select Update Service Reference. Double-click on the app.config file in the WindowsClient project. Visual Studio made the following changes to use the WSDualHttpBinding binding.

<endpoint

  address="https://localhost:8080/WebHost/DuplexDemoService.svc"

  binding="wsDualHttpBinding"

  bindingConfiguration="WSDualHttpBinding_IDuplexDemoService"

  contract="DuplexDemoService.IDuplexDemoService"

  name="WSDualHttpBinding_IDuplexDemoService">

You next need to implement the callback contract in the client application. Select Project | Add Class to display the Add New Item dialog box. Enter DuplexDemoServiceCallback in the Name textbox and click Add.

If you are using C#, add the following code:

class DuplexDemoServiceCallback:

  DuplexDemoService.IDuplexDemoServiceCallback

Right-click IDuplexDemoServiceCallback and select Implement Interface | Implement Interface. Delete the code in the BuyStockCallBack and SellStockCallBack methods.

If you are using Visual Basic, add the following code and press Enter.

Public Class DuplexDemoServiceCallback

  Implements DuplexDemoService.IDuplexDemoServiceCallback

Add the following code to the BuyStockCallBack and SellStockCallBack methods:

System.Windows.Forms.MessageBox.Show(message);

MessageBox.Show(message)

In the Solution Explorer, right-click on the Form1 file and select View Code. Replace the code in the Form1_Load method with the following:

var context = new InstanceContext(

  new DuplexDemoServiceCallback());

proxy = new DuplexDemoServiceClient(context);

Dim context As New InstanceContext( _

  New DuplexDemoServiceCallback)

proxy = New DuplexDemoServiceClient(context)

Recall that to call back to the client, the service needs a reference to the client. This code provides that. The InstanceContext object stores information in the client’s context. This information includes references to the communication channel the client and service use. The code passes this context to the constructor of the proxy class.

Save your changes and build the solution.

Press F5 to run the application. In the form, enter MSFT in the stock text box and 100 in the shares text box. Then click Buy. Next, enter AAPL in the stock text box and 100 in the shares text box. Then click Sell. After a few seconds, you should see a message confirming you bought 100 shares of MSFT. The service has called back to the client to display the confirmation. Click OK to dismiss that message. After a few more seconds, you should see a message confirming you sold 100 shares of AAPL. Click OK to dismiss that message. Close the form.

Conclusion

In this tutorial, you explored the request/reply, one-way and duplex message exchange patterns. In the request-reply pattern, a client application sends a message to a WCF service and then waits for a reply. You will use this pattern most often.

In the one-way message exchange pattern, the client application sends a message to a WCF service but the service does not send a reply message to the client. You can use this pattern when the client does not need to wait for a reply from the service.

In the duplex pattern, both the client and the service can initiate communication. You can use this pattern when you want the service to send a notification or alert to the client after the client has called the service.

The one-way and duplex patterns provide you with opportunities to fine-tune the performance of your applications and to add more flexibility regarding what happens after a client calls a service.

About the Author

Robert Green is a developer, writer, and trainer. He is a senior consultant with MCW Technologies. Robert is a Visual Studio Tools for the Office system MVP and is a co-author of AppDev courseware for Microsoft Visual Basic, Microsoft Visual C#, LINQ and Microsoft Windows Workflow Foundation. Before joining MCW, Robert worked at Microsoft as a Product Manager and also a Program Manager.