Using a Callback Contract to Implement Events


One of the principal uses of a callback contract is to provide a service with a means to inform a client application of a significant occurrence. You can use callbacks to implement an eventing mechanism; the service can advertise events and provide operations to enable client applications to subscribe to these events or unsubscribe from them. The service can use the callback contract to send a message to each subscribing client when an event occurs. You will see how to do this later, but first you need to define, implement, and test a callback contract that can act as the basis for an eventing mechanism.

Add a callback contract to the ProductsService service and invoke a callback operation

  1. Using Visual Studio 2005, open the solution file image from book ProductsService.sln located in the Microsoft Press\WCF Step By Step\Chapter 14\ProductsServiceV3 folder under your \My Documents folder.

    This solution contains version 3 of the ProductsService service. This service contains the operations ListSelectedProducts, GetProduct, CurrentStockLevel, ChangeStockLevel, and a new operation called ChangePrice, which a client application can invoke to change the price of a product. The solution also contains a WPF application for hosting the service, and a client application that you will use to test the ProductsService service and provide an implementation of a callback contract.

  2. In Solution Explorer, open the image from book ProductsService.cs file in the ProductsService project. Add the following callback contract to the file, immediately before the IProductsServiceV3 interface defining the service contract:

     // Callback interface for propagating "price changed" event public interface IProductsServiceCallback {     [OperationContract(IsOneWay = true)]     void OnPriceChanged(Product product); }

    This callback contract contains a single operation called OnPriceChanged. You will modify the ChangePrice operation in the ProductsService service to invoke this operation in a later step. The purpose of this operation is to inform the client of a change in the price of the product passed in as the parameter. Notice that this operation is defined as a oneway operation; all it does is alert the client application and does not return any sort of response.

  3. Modify the ServiceContract attribute for the IProductsServiceV3 interface to reference this callback contract, as shown in bold below:

     // Version 3 of the service contract [ServiceContract(Namespace = "http://adventure-works.com/2006/08/31",                  Name = "ProductsService",                  CallbackContract = typeof(IProductsServiceCallback))] public interface IProductsServiceV3 {      }

    The value to the CallbackContract property must be a type, so this code uses the typeof operator to return the type of the IProductsServiceCallback interface.

  4. Locate the ChangePrice method at the end of the ProductsServiceImpl class (the ProductsServiceImpl class implements the IProductsServiceV3 service contract). This method updates the AdventureWorks database with the new product price, returning true if the update was successful, false otherwise (the method performs very limited error checking). Modify the code near the end of the method to invoke the OnPriceChanged operation in the callback contract if the update succeeds, as shown in bold below:

     … if (numRowsChanged != 0) {     IProductsServiceCallback callback = OperationContext.Current.         GetCallbackChannel<IProductsServiceCallback>();     callback.OnPriceChanged(GetProduct(productNumber));     return true; } else {     return false; }  

  5. Build the ProductsService project.

The next step is to implement the callback contract in the client application, but first you need to generate the proxy code for the client.

Generate the client proxy and implement the callback contract

  1. Generate a proxy class for the client application by using the following procedure:

    • Open a Visual Studio 2005 Command Prompt window and move to the ProductsServiceV3\ProductsService\bin folder in the Microsoft Press\WCF Step By Step\Chapter 14 folder under your \My Projects folder.

    • In the Visual Studio 2005 Command Prompt window, run the command:

       svcutil ProductsService.dll

    • Run the command:

       svcutil /namespace:*,ProductsClient.ProductsService adventure- works.com.2007.03.01.wsdl *.xsd /out:ProductsServiceProxy.cs

  2. Leave the Visual Studio 2005 Command Prompt window open and return to Visual Studio 2005. Add the image from book ProductsServiceproxy.cs file in the ProductsServiceV3\ProductsService\bin folder to the ProductsClient project.

  3. Edit the image from book Program.cs file in the ProductsClient project. Add the following using statement to the list at the top of the file:

     using ProductsClient.ProductsService;

  4. Add the following class to the ProductsClient namespace, below the Program class:

     class Client : ProductsServiceCallback, IDisposable { }

    ProductsServiceCallback is the interface in the proxy that defines the callback contract.

  5. Add the private proxy variable, the public TestProductsService method, and the public Dispose method shown below to the Client class. Replace the text “LON-DEV-01” in the statement that specifies the security domain for client credentials with the name of your own computer:

     private ProductsServiceClient proxy = null; public void TestProductsService() {     // Create a proxy object and connect to the service     proxy = new ProductsServiceClient(new InstanceContext(this),         "WSDualHttpBinding_IProductsServiceV3");     proxy.ClientCredentials.Windows.ClientCredential.Domain = "LON-DEV-01";     proxy.ClientCredentials.Windows.ClientCredential.UserName = "Fred";     proxy.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd";     // Test the operations in the service     try     {         // Obtain a list of frames         Console.WriteLine("Test 1: List all frames");         string[] productNumbers = proxy.ListSelectedProducts("Frame");         foreach (string productNumber in productNumbers)         {             Console.WriteLine("Number: {0}", productNumber);         }         Console.WriteLine();         // Fetch the details for a specific frame         Console.WriteLine("Test 2: Display the details of a frame");         Product product = proxy.GetProduct("FR-M21S-40");         Console.WriteLine("Number: {0}", product.ProductNumber);         Console.WriteLine("Name: {0}", product.Name);         Console.WriteLine("Color: {0}", product.Color);         Console.WriteLine("Price: {0}", product.ListPrice);         Console.WriteLine();         // Modify the price of this frame         Console.WriteLine("Test 3: Modify the price of a frame");         if (proxy.ChangePrice("FR-M21S-40", product.ListPrice + 10))         {             product = proxy.GetProduct("FR-M21S-40");             Console.WriteLine("Price changed. New price: {0}",                               product.ListPrice);         }         else         {             Console.WriteLine("Price change failed");         }         Console.WriteLine();     }     catch (Exception e)     {         Console.WriteLine("Exception: {0}", e.Message);     } } public void Dispose() {     // Disconnect from the service     proxy.Close(); }

    Note 

    This code is available in the file image from book Client.txt located in the Chapter 14 folder.

    The TestProductsService method connects to the ProductsService using an instance of the proxy, fetches and displays a list of bicycle frames, displays the details for a specific frame, and updates the price of that frame. The Dispose method, which is part of the IDisposable interface, closes the proxy when the Client object is disposed.

    Notice that the statement that creates the proxy object creates a new instance of the InstanceContext class as the first parameter to the constructor. The WCF runtime uses this information to construct the information in the operation context on the service, to enable the service to locate the client object when it calls the GetCallbackChannel method. The client application connects to the ProductsService by using the WSDualHttpBinding (you will configure this binding later), using message level security and Windows identities.

  1. Add the following OnPriceChanged method to the Client class, after the Dispose method:

     public void OnPriceChanged(Product product) {     Console.WriteLine("Callback from service: Price of {0} changed to {1}",         product.Name, product.ListPrice); }

    This method implements the operation in the ProductsServiceCallback interface defining the callback contract.

  2. In the Main method in the Program class, add the statements shown below in bold to create a new instance of the Client class and call the TestProductsService method:

     static void Main(string[] args) {     Console.WriteLine("Press ENTER when the service has started");     Console.ReadLine();     Client client = new Client();     client.TestProductsService();     Console.WriteLine("Press ENTER to finish");     Console.ReadLine(); }

Configure the WCF service and client application to use the WSDualHttpBinding binding

  1. Edit the image from book App.config file in the ProductsServiceHost project by using the WCF Configuration Editor.

  2. In the left pane, add a new endpoint to the Products.ProductsServiceImpl service. Set the properties of this endpoint using the values in this table:

    Open table as spreadsheet

    Property

    Value

    Name

    ProductsServiceDualHttpEndpoint

    Address

    http://localhost:8050/ProductsService/ProductsService.svc

    Binding

    wsDualHttpBinding

    Contract

    Products.IProductsServiceV3

    Note 

    By default, the wsDualHttpBinding binding implements message level security and uses Windows identities.

  3. Save the configuration and then exit the WCF Service Configuration Editor.

  4. Add a new application configuration file called image from book App.config to the ProductsClient project.

  5. Edit the image from book App.config file in the ProductsClient project by using the WCF Service Configuration Editor.

  6. In the left pane, add a new endpoint to the Endpoints folder in the Client folder. Set the properties of this endpoint using the values in this table:

    Open table as spreadsheet

    Property

    Value

    Name

    WSDualHttpBinding_IProductsServiceV3

    Address

    http://localhost:8050/ProductsService/ProductsService.svc

    Binding

    wsDualHttpBinding

    Contract

    ProductsClient.ProductsService.ProductsService

  7. Save the configuration and then exit the WCF Service Configuration Editor.

  8. Edit the image from book Program.cs file in the ProductsClient project. Add the statements shown below in bold to the TestProductsService method of the Client class, between the code that creates the proxy object and the statements that set the client credentials:

     proxy = new ProductsServiceClient(new InstanceContext(this),                                   "WSDualHttpBinding_IProductsServiceV3"); WSDualHttpBinding binding = (WSDualHttpBinding)proxy.Endpoint.Binding; binding.ClientBaseAddress =     new Uri("http://localhost:8040/ProductsService/" +              Guid.NewGuid().ToString()); proxy.ClientCredentials.Windows.ClientCredential.Domain = "LON-DEV-01";

    When a WCF client application uses the WSDualHttpBinding binding, the WCF runtime creates two one-way channels using the HTTP transport. By default, the WCF runtime attempts to create a temporary endpoint for the client application to listen for incoming requests by using port 80. If you are running Internet Information Services, then port 80 will already be in use, and when the client attempts to connect to the service it will receive the exception “HTTP could not register URL http://+:80/Temporary_Listen_Address/…/ because TCP port 80 is being used by another application.” You can force the WCF runtime to use a different URL and port by setting the ClientBaseAddress property of the binding. In the example in this exercise, the WCF runtime creates the temporary address based on the URL http://localhost:8040/ProductsService/. The address is supplemented with a GUID to ensure it is unique (if two instances of the client application execute simultaneously on the same computer, as you will be doing later, they cannot both share the same callback URL).

    Note 

    This code is only necessary if you are using the WSDualHttpBinding binding. The other bindings that support callbacks (NetTcpBinding and NetNamedPipeBinding) automatically provide two-way communications.

  9. Start the solution without debugging. In the ProductsServiceHost form, click Start. In the client application console window, press Enter.

    The client application displays a list of frames, followed by the details for the frame with product number FR-M21S-40. The code then adds 10 to the price of the frame and invokes the ChangePrice operation with this new price. Notice that after Test 3 starts, the message “Callback from service: Price of LL Mountain Frame – Silver, 40 changed to 364.0500” appears, output by the OnPriceChanged operation invoked by the service, as shown in the following image:

    image from book

    Note 

    The price displayed might be different if you have previously modified the data in the AdventureWorks database. The important points are that this message appears, and that the price has increased by 10 from the value displayed in Test 2.

    The client application outputs the message “Price changed. New price: 364.0500” after the ChangePrice operation completes (and after the message displayed by the OnPriceChanged method).

  10. Press Enter to close the client application console window. In the ProductsServiceHost form, click Stop, and then close the form.

The callback contract enables the service to confirm to the client application that the product price has changed, but the client application probably already knew this because it initiated the change! It is arguably more important for other concurrent instances of the client application to be informed of this update. Now that you have seen how to implement and invoke a client callback and test that it works as expected, you can extend this idea to invoke the callback operation in other instances of the client application. To do this, the service must have a reference to each client application instance. In the following exercises, you will modify the ProductsService service to enable client application instances to register their interest in product price changes by adding a subscribe operation. The purpose of this operation is simply to cache a reference to the client application instance that the service can use later to invoke the OnPriceChanged operation. You will also add an unsubscribe operation to enable a client application instance to remove itself from the list that the service notifies.

Add subscribe and unsubscribe operations to the WCF service

  1. In Visual Studio 2005, edit the image from book ProductsService.cs file in the ProductsService project.

  2. Add the SubscribeToPriceChangedEvent and UnsubscribeFromPriceChangedEvent methods shown below in bold to the end of the IProductsServiceV3 service contract:

     [ServiceContract(Namespace = "http://adventure-works.com/2006/08/31",                  Name = "ProductsService",                  CallbackContract = typeof(IProductsServiceCallback))] public interface IProductsServiceV3 {     …     // Subscribe to the "price changed" event     [OperationContract]     bool SubscribeToPriceChangedEvent();      // Unsubscribe from the "price changed" event    [OperationContract]     bool UnsubscribeFromPriceChangedEvent(); }

    Client applications will use the SubscribeToPriceChangedEvent operation to declare an interest in product price changes and the UnsubscribeFromPriceChangedEvent operation to indicate that they are no longer interested in product price changes.

  3. Add the following private variable to the ProductsServiceImpl class:

     public class ProductsServiceImpl : IProductsServiceV3 {     static List<IProductsServiceCallback> subscribers =         new List<IProductsServiceCallback>();      }

    The ProductsServiceImpl class will add references to client callbacks to this list for each client application instance that indicates its interest in product price changes.

  4. Add the SubscribeToPriceChanged method shown below to the ProductsServiceImpl class:

     public bool SubscribeToPriceChangedEvent() {     try     {         IProductsServiceCallback callback = OperationContext.Current.             GetCallbackChannel<IProductsServiceCallback>();         if (!subscribers.Contains(callback))         {             subscribers.Add(callback);         }         return true;     }     catch (Exception)     {         return false;     } }

    This method obtains a reference to the callback contract for the client application instance invoking the operation and stores it in the subscribers list. If the callback contract reference is already in the list, this method does not add it again.

  5. Add the UnsubscribeFromPriceChangedEvent method to the ProductsServiceImpl class, as follows:

     public bool UnsubscribeFromPriceChangedEvent() {     try     {         IProductsServiceCallback callback = OperationContext.Current.             GetCallbackChannel<IProductsServiceCallback>();         subscribers.Remove(callback);         return true;     }     catch (Exception)     {         return false;     } }

    This method removes the callback reference for the client application instance invoking the operation from the subscribers list.

  6. Add the private method shown below to the ProductsServiceImpl class:

     private void raisePriceChangedEvent(Product product) {     subscribers.ForEach(delegate(IProductsServiceCallback callback)     {         if (((ICommunicationObject)callback).State ==                 CommunicationState.Opened)         {             callback.OnPriceChanged(product);         }         else         {             subscribers.Remove(callback);         }     }); }

    This method iterates through all the callback references in the subscribers list. If the reference is still valid (the client application instance is still running), it invokes the OnPriceChanged operation, passing in the specified product as the parameter. If the reference is not valid, the method removes it from the list of subscribers.

  7. In the ChangePrice method, comment out the statements that obtain the callback reference to the client application, invoke the OnPriceChanged method, and add a statement that calls the raisePriceChangedEvent method instead. Pass the product whose price has changed as the parameter to the raisePriceChangedEvent method, like this:

     if (numRowsChanged != 0) {    //IProductsServiceCallback callback = OperationContext.Current.    //    GetCallbackChannel<IProductsServiceCallback>();    //callback.OnPriceChanged(GetProduct(productNumber));    raisePriceChangedEvent(GetProduct(productNumber));    return true; }

    When a client application instance changes the price of a product, all client application instances that have subscribed to the “price changed” event will be notified by running the OnPriceChanged method.

  8. Rebuild the ProductsService project.

Update the WCF client application to subscribe to the “Price Changed” event

  1. Regenerate the proxy class for the client application:

    • In the Visual Studio 2005 Command Prompt window, run the command:

       svcutil ProductsService.dll

    • Run the command:

       svcutil /namespace:*,ProductsClient.ProductsService adventure- works.com.2007.03.01.wsdl *.xsd /out:ProductsServiceProxy.cs

  1. Close the Visual Studio 2005 Command Prompt window, and return to Visual Studio 2005. Delete the image from book ProductsServiceproxy.cs file from the ProductsClient project and add the new version of this file from the ProductsServiceV3\ProductsService\bin folder.

  2. Edit the image from book Program.cs file in the ProductsClient project. Invoke the SubscribeToPriceChangedEvent operation as the first action inside the try block in the TestProductsService method in the Client class:

     try {     proxy.SubscribeToPriceChangedEvent();     // Obtain a list of frames     Console.WriteLine("Test 1: List all frames");     string[] productNumbers = proxy.ListSelectedProducts("Frame");      }

    Whenever any instance of the client application updates the price of a product, the service will call the OnPriceChanged method in this instance of the client application.

  3. Rebuild the ProductsClient project.

Test the “Price Changed” event in the WCF service

  1. In Solution Explorer, right-click the ProductsServiceHost project, point to Debug, and click Start new instance. In the ProductsServiceHost form, click Start.

  2. Using Windows Explorer, move to the Microsoft Press\WCF Step By Step\Chapter 14\ProductsClient\bin\Debug folder under your \My Documents folder.

    Apart from the executable and configuration files, you should notice a command file called image from book RunClients.cmd. This command file simply runs the ProductsClient application concurrently, three times, each time opening a new window, like this:

     start ProductsClient start ProductsClient start ProductsClient

  3. Double-click the image from book RunClients.cmd file. Three console windows appear, one for each instance of the client application. In one console window, press Enter. Wait for the list of bicycle frames to appear, the details of frame FR-M21S-40 to be displayed, and the price of the frame to be changed. Verify that the message “Callback from service: Price of LL Mountain Frame – Silver, 40 changed to 374.0500” appears. Leave this command window open (do not press Enter).

  4. In one of the other two console windows, press Enter. Again, wait while the list of frames and the details of frame FR-M21S-40 are displayed and the price of the frame is updated. Verify that the callback message appears in this client console window. Notice that a second callback message appears in the first client console window, also displaying the new price.

  5. In the final console window, press Enter. Verify that when this instance of the client application updates the price of the bicycle frame and displays the callback message, the other two client console windows also output the callback message. The first client console window should display three callback messages, like this:

    image from book

  6. Press Enter in each of the client application console windows to close them. In the ProductsServiceHost form, click Stop, and then close the form.

Delivery Models for Publishing and Subscribing

Using a callback contract makes it very easy to implement a basic publication and subscription service based on WCF. You should be aware that you have been using a somewhat artificial and idealized configuration for these exercises. If you are implementing such a system in a large enterprise, or across the Internet, you have to consider issues of security and scalability and how they can impact the operation of a WCF service calling back into a client application. There are at least three well-known models that publication and subscription systems frequently implement, and you can use WCF to build systems based on any of them. Each model has its own advantages and disadvantages, as described in the following sections.

The Push Model

This is the model you have used in the exercises in this chapter. In this model, the publisher (the WCF service) sends messages directly to each subscriber (WCF client applications) through an operation in a callback contract. The service must have sufficient resources to be able to invoke operations in a potentially large number of subscribers simultaneously; the service could spawn a new thread for each subscriber if the callback operations return data or could make use of one-way operations if not. The primary disadvantage of this approach is security; the callback operations invoked by the service could be blocked by a firewall protecting client applications from unexpected incoming messages.

The Pull Model

In this model, the publisher updates a single, trusted third service with information about events as they occur. Each subscriber periodically queries this third service for updated information (they invoke an operation on the third service that returns the latest version of the data). This model is less prone to f irewall blocking issues but requires more complexity on t he part of the subscribers. There could also be scalability issues with the third service if a large number of subscribers query it too frequently. On the other hand, if a subscriber does not query the third site frequently enough, it might miss an event.

The Broker Model

This model is a hybrid combination of the first two schemes. The publisher updates a single, trusted third service with information about events as they occur. This third site is placed in a location, such as a perimeter network, trusted by the publishing service and the subscribing clients. Subscribers actually register with the third site rather than the site originating events. The third site calls back into subscribers when an event occurs. As well as reducing the likelihood of messages being blocked by a firewall, this model also resolves some of the scalability issues associated with subscribers polling for updated information too quickly.

Note 

You can also make use of Windows Network Load Balancing and clustering technologies to overcome some of the scalability concerns when using the Pull or Broker models.




Microsoft Windows Communication Foundation Step by Step
Microsoft Windows Communication Foundation Step by Step (Step By Step Developer Series)
ISBN: 0735623368
EAN: 2147483647
Year: 2007
Pages: 105
Authors: John Sharp

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net