Implementing and Invoking a Client Callback


A client callback is an operation implemented by a client application that a service can invoke. This is a reverse of the more traditional mechanism for exchanging messages and requires that the client application is listening for requests from the service. However, while a service listens to an endpoint, using the WCF service infrastructure established when the host application opens a ServiceHost object, a client application does not use a ServiceHost object, and it normally only expects to receive messages in response to explicit requests that it sends. The question is how can a client application listen for requests and at the same time continue its own processing? WCF provides two features that you can use to implement this functionality: callback contracts and duplex channels.

Defining a Callback Contract

A callback contract specifies the operations that a service can invoke in a client application. A callback contract is very similar to a service contract inasmuch as it contains operations marked with the OperationContract attribute. The main syntactic difference is that you do not decorate it with the ServiceContract attribute. Here is an example:

 public interface IProductsServiceCallback {     // Inform the client application that the price of the specified     // product has changed     [OperationContract]     void OnPriceChanged(Product product); }

Client applications are expected to provide an implementation of each method in the callback contract. You reference a callback contract from a service contract defining the operations implemented by the service by using the CallbackContract property of the ServiceContract attribute, like this:

 [ServiceContract(…, CallbackContract=typeof(IProductsServiceCallback)] public interface IProductsServiceV3 {     // Any method in this contract can invoke the OnPriceChanged method     // in the client application     [OperationContract]     List<string> ListSelectedProducts(string match);          [OperationContract]     bool ChangePrice(string productNumber, decimal price);      }

The service can invoke any of the operations in the callback contract from methods implementing the service contract. You can only associate a single callback contract with a service contract.

Implementing an Operation in a Callback Contract

The callback contract defines the operations that the service can invoke in a client application, but you must also provide a means for the service to actually invoke these operations. If you create a client proxy class for the service by using the svcutil utility, the class is based on the generic System.ServiceModel.DuplexClientBase class (an ordinary client proxy extends the ClientBase generic class, as described in Chapter 10, “Programmatically Controlling the Configuration and Communications”). An abbreviated version of the proxy code for the IProductsServiceV3 interface looks like this:

 [System.ServiceModel.ServiceContractAttribute(…,     CallbackContract=typeof(ProductsServiceCallback))] public interface ProductsService {     [System.ServiceModel.OperationContractAttribute(Action=,                                                     ReplyAction=)]     string[] ListSelectedProducts(string match);          [System.ServiceModel.OperationContractAttribute(Action=,                                                     ReplyAction=)]     bool ChangePrice(string productNumber, decimal price);      }  public interface ProductsServiceCallback {     [OperationContractAttribute(Action=)]     void OnPriceChanged(Product product); } public partial class ProductsServiceClient :     DuplexClientBase<ProductsService>, ProductsService {     ProductsServiceClient(InstanceContext callbackInstance) :             base(callbackInstance)    {    }    public ProductsServiceClient(InstanceContext callbackInstance,        string endpointConfigurationName) :            base(callbackInstance, endpointConfigurationName)    {    }    // Other constructors not shown        public string[] ListSelectedProducts(string match)    {        return base.Channel.ListSelectedProducts(match);    }        bool ChangePrice(string productNumber, decimal price);    {        return base.Channel.ChangePrice(productNumber,price);    }     }

The bold statements highlight the important differences between this code and the code for a proxy that does not define a callback contract. The client application must provide a class that implements the ProductsServiceCallback interface, including the OnPriceChanged method.

The ProductsServiceClient proxy class extends the DuplexClientBase<ProductsService> class and defines a number of constructors that the client application can use to instantiate a proxy object. The code fragment shows only two of these constructors, but the main feature is that they all expect you to provide an InstanceContext object as the first parameter. This is the key property that enables the service to invoke the operation in the client application.

You should already be familiar with the concept of instance context for a service; each instance of a service runs in its own context holding the state information (instance variables and pieces of system information) for that instance. Different instances of a service have their own context. The WCF runtime creates and initializes this context automatically when it instantiates the service instance. A client application implementing a callback contract must also provide an instance context that the service can use to invoke the operations in this instance of the client. You create and provide this context, wrapped up as an InstanceContext object, to the constructor of the proxy. When the client application sends a request message to the service, the WCF runtime automatically sends the client context with the request. If the service needs to invoke an operation in the callback contract, it uses this context object to direct the call to the appropriate instance of the client application (you will see how to do this shortly).

Here is the code for part of a client application that implements the ProductsServiceCallback interface defined in the client proxy:

 class Client : ProductsServiceCallback, IDisposable {     private ProductsServiceClient proxy = null;     public void DoWork()     {         // Create a proxy object and connect to the service         InstanceContext context = new InstanceContext(this);        proxy = new ProductsServiceClient(context, );                        // Invoke operations        bool result = proxy.ChangePrice();             }     public void Dispose()     {        // Disconnect from the service        proxy.Close();     }     // Method specified in the ProductsServiceCallback interface     public void OnPriceChanged(Product product)     {         Console.WriteLine("Price of {0} changed to {1}",             product.Name, product.ListPrice);     } }

The parameter specified for the InstanceContext constructor (this) is a reference to the object implementing the ProductsServiceCallback contract. The statement that creates the proxy object in the DoWork method references this InstanceContext object. If the service invokes the OnPriceChanged operation through this context object, the WCF runtime will call the method on this instance of the client application.

Notice that the client class also implements the IDisposable interface. The Dispose method closes the proxy. A service could potentially call back into the client application at any time after the Client object has sent an initial message. If the client application closes the proxy immediately after sending requests to the service in the DoWork method, the service will fail if it attempts to call back into the Client object. The Client object continues to exist after the DoWork method finishes, and closing the proxy in the Dispose method enables a service to invoke operations in the Client object at any time until the client application terminates or it explicitly disposes the Client object.

Invoking an Operation in a Callback Contract

To invoke an operation in a callback contract, a service must obtain a reference to the client application object implementing the callback contract. This information is available in the operation context for the service. You can access the operation context through the static OperationContext.Current property, which returns an OperationContext object. The OperationContext class provides the generic GetCallbackChannel method, which in turn returns a reference to a channel that the service can use to communicate with the instance of the client application that invoked the service. The value returned by the GetCallbackChannel method is a typed reference to the callback contract, and you can invoke operations through this reference, like this:

 // WCF service class that implements the service contract public class ProductsServiceImpl : IProductsServiceV3 {     …     public bool ChangePrice(string productNumber, decimal price)     {         // Update the price of the product in the database         …         // Invoke the callback operation in the client application         IProductsServiceCallback callback = OperationContext.Current.             GetCallbackChannel<IProductsServiceCallback>();         callback.OnPriceChanged(GetProduct(productNumber));              } }

It is possible that the client application could terminate between invoking the operation in the service and the service calling back into the service, especially if the operation in the service is a one-way operation. You should therefore check to ensure that the callback channel has not been closed before invoking a callback operation:

 IProductsServiceCallback callback = OperationContext.Current.     GetCallbackChannel<IProductsServiceCallback>(); if (((ICommunicationObject)callback).State == CommunicationState.Opened) {      callback.OnPriceChanged(GetProduct(productNumber)); } 

All WCF channels implement the ICommunicationObject interface. This interface provides the State property, which you can use to determine whether the channel is still open or not. If the value of this property is anything other than CommunicationState.Opened, then the service should not attempt to use the callback.

Note 

Channels exhibit the same set of states and state transitions that a ServiceHost object does (the ServiceHost class indirectly implements the ICommunicationObject interface). Refer to Chapter 3, “Making Applications and Services Robust,” for a description of these states.

Reentrancy and Threading in a Callback Operation

If a service invokes an operation in a callback contract, it is possible for the client code implementing that contract to make another operation call into the service. By default, the WCF runtime in the client handling the callback contract executes using a single thread, and calling back into the service could possibly result in the service blocking the thread processing the initial request. In this case, the WCF runtime detects the situation and throws an InvalidOperationException exception, with the message “This operation would deadlock because the reply cannot be received until the current Message completes processing.” To prevent this situation from arising, you can set the concurrency mode of the class implementing the callback contract in the client application either to enable multiple threading (if the client application code is thread-safe) or enable reentrancy (if the client application code is not thread-safe, but the data it uses remains consistent across calls). You achieve this by applying the CallbackBehavior attribute to the class in the client application implementing the callback contract and setting the ConcurrencyMode property to ConcurrencyMode.Multiple or ConcurrencyMode.Reentrant:

 [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)] class Client : ProductsServiceCallback, IDisposable {     … }

Implementing a Duplex Channel

Not all bindings support client callbacks. Specifically, you must use a binding that supports bidirectional communications; either end of the connection must be able to initiate communications, and the other end must be able to accept them. Transports such as TCP and named pipes are inherently bidirectional, and you can use the NetTcpBinding and NetNamedPipeBinding bindings with a client callback. However, the model implemented by the HTTP protocol does not support this mode of operation, so you cannot use the BasicHttpBinding or WSHttpBinding bindings. This sounds like a major shortcoming if you want to build an Intranet system based on the HTTP transport. However, WCF provides the WSDualHttpBinding binding for this purpose. The WSDualHttpBinding binding establishes two HTTP channels (one for sending requests from the client application to the service, and the other for the service to send requests to the client application) but hides much of the complexity from you, so you can treat it as a single bidirectional channel.

There are some important differences between the WSHttpBinding binding and the WSDualHttpBinding binding. Specifically, the WSDualHttpBinding binding does not support transport level security, but it always implements reliable sessions (you cannot disable them).




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