Callback Operations

WCF supports allowing a service to call back to its clients. During a callback, in many respects the tables are turned: the service is the client, and the client becomes the service (see Figure 5-1). Callback operations can be used in a variety of scenarios and applications, but they are especially useful when it comes to events, or notifying the client(s) that some event has happened on the service side.

A callback allows the service to call back to the client

Figure 5-1. A callback allows the service to call back to the client

Callbacks are also commonly referred to as duplex operations. There are two immediate challenges to supporting duplex communication. First, how does the service know where the callback endpoint is? Second, how does the client facilitate hosting the callback object?

Not all bindings support callback operations. Only bidirectional-capable bindings support callback operations. For example, because of its connectionless nature, HTTP cannot be used for callbacks, and therefore you cannot use callbacks over the BasicHttpBinding or the WSHttpBinding. To support callbacks over HTTP, WCF offers the WSDualHttpBinding, which actually sets up two WS channels: one for the calls from the client to the service and one for the calls from the service to the client. WCF also offers callback support for the NetTcpBinding and the NetNamedPipeBinding, because by their very nature, the TCP and the IPC protocols support duplex communication.

Duplex callbacks are nonstandard, as there is no industry standard that states how the client endpoint reference is passed to the service, or how the service publishes the callback contract in the first place. Duplex callbacks (including those over the WS dual binding) are a pure Microsoft feature.

The Callback Contract

Callback operations are part of the service contract, and it is up to the service contract to define its own callback contract. A service contract can have at most one callback contract. Once defined, the clients are required to support the callback and provide the callback endpoint to the service in every call. To define a callback contract, the ServiceContract attribute offers the CallbackContract property of the type Type:

[AttributeUsage(AttributeTargets.Interface|AttributeTargets.Class)]
public sealed class ServiceContractAttribute : Attribute
{
   public Type CallbackContract
   {get;set;}
   //More members
}

When you define a service contract with a callback contract, you need to provide the ServiceContract attribute with the type of the callback contract and the definition of the callback contract, as shown in Example 5-1.

Example 5-1. Defining and configuring a callback contract

interface ISomeCallbackContract
{
   [OperationContract]
   void OnCallback( );
}

[ServiceContract(CallbackContract = typeof(ISomeCallbackContract))]
interface IMyContract
{
   [OperationContract]
   void DoSomething( );
}

Note that the callback contract need not be marked with the ServiceContract attributeā€”the ServiceContract attribute is implied because it is defined as a callback contract and will be included in the service metadata. Of course, you still need to mark all the callback interface methods with the OperationContract attribute.

When the client imports the metadata of the callback contract, the imported callback interface will not have the same name on the client as in the original service-side definition. The name on the client will be the name of the service contract interface, suffixed with the word Callback. For example, a client that imports the definitions of Example 5-1 will end up with these definitions instead:

interface IMyContractCallback
{
   [OperationContract]
   void OnCallback( );
}
[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
interface IMyContract
{
   [OperationContract]
   void DoSomething( );
}

Tip

For simplicity's sake, I recommend using this naming convention also on the service side (i.e., naming the callback contract with the service contract interface name suffixed by Callback).

Client Callback Setup

It is up to the client to host the callback object and expose a callback endpoint. Recall from Chapter 1 that the innermost execution scope of the service instance is the instance context. The InstanceContext class provides a constructor that takes the service instance to the host:

public sealed class InstanceContext : CommunicationObject,...
{
   public InstanceContext(object implementation);
   public object GetServiceInstance( );
   //More members
}

All the client needs to do to host a callback object is instantiate the callback object and construct a context around it:

class MyCallback : IMyContractCallback
{
   public void OnCallback( )
   {...}
}
IMyContractCallback callback = new MyCallback( );
InstanceContext context = new InstanceContext(callback);

It is also worth mentioning that although the callback methods are on the client side, they are WCF operations in every respect and therefore have an operation call context, accessible via OperationContext.Current.

Duplex proxies

Whenever a client is interacting with a service endpoint whose contract defines a callback contract, the client must use a proxy that will set up the bidirectional communication and pass the callback endpoint reference to the service. To that end, the proxy the client uses must derive from the specialized proxy class DuplexClientBase<T>, shown in Example 5-2.

Example 5-2. The DuplexClientBase<T> class

public interface IDuplexContextChannel : IContextChannel
{
   InstanceContext CallbackInstance
   {get;set;}
   //More members
}
public abstract class DuplexClientBase<T> : ClientBase<T> where T : class
{
   protected DuplexClientBase(InstanceContext callbackContext);
   protected DuplexClientBase(InstanceContext callbackContext,string endpointName);
   protected DuplexClientBase(InstanceContext callbackContext,Binding binding,
                              EndpointAddress remoteAddress);
   protected DuplexClientBase(object callbackInstance);
   protected DuplexClientBase(object callbackInstance,string endpointName);
   protected DuplexClientBase(object callbackInstance,Binding binding,
                              EndpointAddress remoteAddress);

   public IDuplexContextChannel InnerDuplexChannel
   {get;}
   //More members
}

The client needs to provide the constructor of DuplexClientBase<T> with the instance context hosting the callback object (as well as the service endpoint information, as with a regular proxy). The proxy will construct an endpoint around the callback context, while inferring the details of the callback endpoint from the service endpoint configuration: the callback endpoint contract is the one defined by the service contract callback type. The callback endpoint will use the same binding (and transport) as the outgoing call. For the address, WCF will use the client's machine name, and it will even select a port when using HTTP. Simply passing the instance context to the duplex proxy and using the proxy to call the service will expose that client-side callback endpoint. To streamline the process, DuplexClientBase<T> also offers constructors that accept the callback object directly and wrap it internally with a context. If for any reason the client needs to access that context, DuplexClientBase<T> additionally offers the InnerDuplexChannel property of the type IDuplexContextChannel, which provides access to the context via the CallbackInstance property.

When you use Visual Studio 2008 to generate a proxy class targeting a service with a callback contract, the tool will generate a class that derives from DuplexClientBase<T>, as shown in Example 5-3.

Example 5-3. VS 2008-generated duplex proxy

class MyContractClient : DuplexClientBase<IMyContract>,IMyContract
{
   public MyContractClient(InstanceContext callbackContext) : base(callbackContext)
   {}
public MyContractClient(InstanceContext callbackContext,string endpointName) :
                                                 base(callbackContext,endpointName)
   {}
   public MyContractClient(InstanceContext callbackContext,Binding binding,
                           EndpointAddress remoteAddress) :
                                        base(callbackContext,binding,remoteAddress)
   {}
   //More constructors

   public void DoSomething( )
   {
      Channel.DoSomething( );
   }
}

Using that derived proxy class, the client can construct a callback instance, host it in a context, create a proxy, and call the service, thus passing the callback endpoint reference:

class MyCallback : IMyContractCallback
{
   public void OnCallback( )
   {...}
}
IMyContractCallback callback = new MyCallback( );
InstanceContext context = new InstanceContext(callback);

MyContractClient proxy = new MyContractClient(context);
proxy.DoSomething( );

Note that as long as the client is expecting callbacks, the client cannot close the proxy. Doing so will close the callback endpoint and cause an error on the service side when the service tries to call back.

It is often the case that the client itself implements the callback contract, in which case the client will typically use a member variable for the proxy and close it when the client is disposed of, as shown in Example 5-4.

Example 5-4. Client implementing the callback contract

class MyClient : IMyContractCallback,IDisposable
{
   MyContractClient m_Proxy;

   public void CallService( )
   {
      InstanceContext context = new InstanceContext(this);
      m_Proxy = new MyContractClient(context);
      m_Proxy.DoSomething( );
   }
   public void OnCallback( )
   {...}
public void Dispose( )
   {
      m_Proxy.Close( );
   }
}

The generated proxy does not take advantage of the streamlined constructors of DuplexClientBase<T> that accept the callback object directly, but you can rework the proxy manually to add that support, as shown in Example 5-5.

Example 5-5. Using a reworked object-based proxy

class MyContractClient : DuplexClientBase<IMyContract>,IMyContract
{
   public MyContractClient(object callbackInstance) : base(callbackInstance)
   {}
   //More constructors
   public void DoSomething( )
   {
     Channel.DoSomething( );
   }
}
class MyClient : IMyContractCallback,IDisposable
{
   MyContractClient m_Proxy;

   public void CallService( )
   {
      m_Proxy = new MyContractClient(this);
      m_Proxy.DoSomething( );
   }
   public void OnCallback( )
   {...}
   public void Dispose( )
   {
      m_Proxy.Close( );
   }
}

Service-Side Callback Invocation

The client-side callback endpoint reference is passed along with every call the client makes to the service, and it is part of the incoming message. The OperationContext class provides the service with easy access to the callback reference via the generic method GetCallbackChannel<T>( ):

public sealed class OperationContext : ...
{
   public T GetCallbackChannel<T>( );
   //More members
}

Exactly what the service does with the callback reference and when it decides to use it is completely at the discretion of the service. The service can extract the callback reference from the operation context and store it for later use, or it can use it during the service operation to call back to the client. Example 5-6 demonstrates the first option.

Example 5-6. Storing the callback references for later use

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyService : IMyContract
{
   static List<IMyContractCallback> m_Callbacks = new List<IMyContractCallback>( );
   public void DoSomething( )
   {
      IMyContractCallback callback = OperationContext.Current.
                                        GetCallbackChannel<IMyContractCallback>( );

      if(m_Callbacks.Contains(callback) == false)
      {
         m_Callbacks.Add(callback);
      }
   }
   public static void CallClients( )
   {
      Action<IMyContractCallback> invoke = (callback)=>
                                           {
                                              callback.OnCallback( );
                                           };
      m_Callbacks.ForEach(invoke);
   }
}

The service uses a static, generic linked list to store references to interfaces of the type IMyContractCallback. Because the service is not aware of which client is calling it and whether or not the client has called it already, in every call the service checks to see whether the list already contains the passed-in callback reference. If the list does not contain the reference, the service adds the callback to the list.

The service class also offers the static method CallClients( ), which any party on the host side can use to call back to the clients:

MyService.CallClients( );

Here, the invoking party is using some host-side thread for the callback invocation. That thread is unrelated to any thread executing the incoming service call.

Tip

Example 5-6 (and similar examples in this chapter) does not synchronize access to the callback list. Obviously, real application code will need to do that. Concurrency management (and, in particular, synchronizing access to shared resources) is discussed in Chapter 8.

Callback reentrancy

The service may also want to invoke the callback reference that's passed in (or a saved copy of it) during the execution of a contract operation. However, such invocations are disallowed by default. The reason is the default service concurrency management. By default, the service class is configured for single-threaded access: the service instance context is associated with a lock, and only one thread at a time can own the lock and access the service instance inside that context. Calling out to the client during an operation call requires blocking the service thread and invoking the callback. The problem is that processing the reply message from the client on the same channel once the callback returns requires reentering the same context and negotiating ownership of the same lock, which will result in a deadlock. Note that the service may still invoke callbacks to other clients or call other services; it is the callback to its calling client that will cause the deadlock.

To prevent such a deadlock, if the single-threaded service instance tries to call back to its client, WCF will throw an InvalidOperationException. There are three possible solutions. The first is to configure the service for multithreaded access. Callbacks to the calling client will then be allowed because the service instance will not be associated with a lock; however, this will increase the burden on the service developer, because of the need to provide synchronization for the service. The second solution is to configure the service for reentrancy. When configured for reentrancy, the service instance context is still associated with a lock, and only single-threaded access is allowed. However, if the service is calling back to its client, WCF will silently release the lock first. Chapter 8 is dedicated to the synchronization modes and their implications on the programming model. For now, all you need to know is that if your service needs to call back to its clients, you can set its concurrency behavior to either multithreaded or reentrant using the ConcurrencyMode property of the ServiceBehavior attribute:

public enum ConcurrencyMode
{
   Single, //Default
   Reentrant,
   Multiple
}

[AttributeUsage(AttributeTargets.Class)]
public sealed class ServiceBehaviorAttribute : ...
{
   public ConcurrencyMode ConcurrencyMode
   {get;set;}
   //More members
}

Example 5-7 demonstrates a service configured for reentrancy. During the operation execution, the service accesses the operation context, grabs the callback reference, and invokes it. Control will only return to the service once the callback returns, and the service's own thread will need to reacquire the lock.

Example 5-7. Configuring for reentrancy to allow callbacks

[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
interface IMyContract
{
   [OperationContract]
   void DoSomething( );
}
interface IMyContractCallback
{
   [OperationContract]
   void OnCallback( );
}
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
class MyService : IMyContract
{
   public void DoSomething( )
   {
      IMyContractCallback callback = OperationContext.Current.
                                      GetCallbackChannel<IMyContractCallback>( );
      callback.OnCallback( );
   }
}

The third solution that allows the service to safely call back to the client is to configure the callback contract operations as one-way operations. Doing so will enable the service to call back even when the concurrency mode is set to single-threaded, because there will not be any reply message to contend for the lock. Example 5-8 demonstrates this configuration. Note that the service defaults to single-threaded concurrency mode.

Example 5-8. One-way callbacks are allowed by default

[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
interface IMyContract
{
   [OperationContract]
   void DoSomething( );
}
interface IMyContractCallback
{
   [OperationContract(IsOneWay = true)]
   void OnCallback( );
}
class MyService : IMyContract
{
   public void DoSomething( )
   {
      IMyContractCallback callback = OperationContext.Current.
                                      GetCallbackChannel<IMyContractCallback>( );
      callback.OnCallback( );
   }
}

Callback Connection Management

The callback mechanism supplies nothing like a higher-level protocol for managing the connection between the service and the callback endpoint. It is up to the developer to come up with some application-level protocol or a consistent pattern for managing the lifecycle of the connection. As mentioned previously, the service can only call back to the client if the client-side channel is still open, which is typically achieved by not closing the proxy. Keeping the proxy open will also prevent the callback object from being garbage-collected. If the service maintains a reference on a callback endpoint and the client-side proxy is closed or the client application itself is gone, when the service invokes the callback it will get an ObjectDisposedException from the service channel. It is therefore preferable for the client to inform the service when it no longer wishes to receive callbacks or when the client application is shutting down. To that end, you can add an explicit Disconnect( ) method to the service contract. Since every method call carries the callback reference with it, in the Disconnect( ) method the service can remove the callback reference from its internal store.

In addition, for symmetry's sake, I recommend adding an explicit Connect( ) method. Having a Connect( ) method will enable the client to connect or disconnect multiple times, as well as provide a clearly delineated point in time as to when to expect a callback (only after a call to Connect( )). Example 5-9 demonstrates this technique. In both the Connect( ) and Disconnect( ) methods, the service needs to obtain the callback reference. In Connect( ), the service verifies that the callback list does not already contain the callback reference before adding it to the list (this makes multiple calls to Connect( ) benign). In Disconnect( ), the service verifies that the list contains the callback reference, and it throws an exception otherwise.

Example 5-9. Explicit callback connection management

[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
interface IMyContract
{
   [OperationContract]
   void DoSomething( );

   [OperationContract]
   void Connect( );

   [OperationContract]
   void Disconnect( );
}
interface IMyContractCallback
{
   [OperationContract]
   void OnCallback( );
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyService : IMyContract
{
   static List<IMyContractCallback> m_Callbacks = new List<IMyContractCallback>( );

   public void Connect( )
   {
      IMyContractCallback callback = OperationContext.Current.
                                        GetCallbackChannel<IMyContractCallback>( );
      if(m_Callbacks.Contains(callback) == false)
      {
         m_Callbacks.Add(callback);
      }
   }
   public void Disconnect( )
   {
      IMyContractCallback callback = OperationContext.Current.
                                        GetCallbackChannel<IMyContractCallback>( );
      if(m_Callbacks.Contains(callback) == true)
      {
         m_Callbacks.Remove(callback);
      }
      else
      {
         throw new InvalidOperationException("Cannot find callback");
      }
   }
   public static void CallClients( )
   {
      Action<IMyContractCallback> invoke = (callback)=>
                                           {
                                              callback.OnCallback( );
                                           };
      m_Callbacks.ForEach(invoke);
   }
   public void DoSomething( )
   {...}
}

Connection management and instance mode

A per-call service can use a callback reference during the operation call itself, or store it in some kind of a global repository such as a static variable, as you have seen in the examples so far. The per-call service must use some static variable to store the reference, since any instance state the service may use to store the reference will be gone when the operation returns. Using a Disconnect( )-like method is therefore especially required for per-call services, as without it, the shared store will become bloated over time with dead callback references. A similar need exists with a singleton service. The singleton lifetime has no end, so it will accumulate an unlimited number of callback references, and as time goes by most of them will become stale because the callback clients will no longer be running. Having a Disconnect( ) method will keep the singleton connected only to the relevant alive clients.

Interestingly enough, a per-session service may get by without a Disconnect( ) method, as long as it maintains the callback reference in some instance member variable. The reason is that the service instance will automatically be disposed of when the session ends (when the client closes the proxy or times out), and there is no danger in keeping the reference throughout the session, as it is guaranteed to always be valid. However, if the sessionful service stores its callback reference in some global repository for the use of other host-side parties or across sessions, adding a Disconnect( ) method is required in order to remove the callback reference explicitly, because the callback reference is not available during the call to Dispose( ).

You may also want to add the Connect( ) and Disconnect( ) pair on a sessionful service simply as a feature, because it enables the client to decide when to start or stop receiving callbacks during the session.

The Duplex Proxy and Type Safety

The WCF-provided DuplexClientBase<T> is not strongly typed to the callback interface used. The compiler will let you pass in any object, including an invalid callback interface. The compiler will even let you use for T a service contract type that has no callback contract defined at all. At runtime, you can successfully instantiate the proxy. The incompatibility will be discovered only when you try to use the proxy, yielding an InvalidOperationException. Much the same way, InstanceContext is object-based and is not verified at compile time to actually have a valid callback contract instance. When it's passed as a constructor parameter to the duplex proxy, there is no compile-time check to correlate the InstanceContext with the callback instance the duplex proxy expects, and the error will be discovered only when you try to use the proxy. You can use generics to compensate to some degree for these oversights and discover the error at runtime, as soon as you declare the proxy.

First, define the type-safe, generic InstanceContext<T> class, shown in Example 5-10.

Example 5-10. The InstanceContext<T> class

public class InstanceContext<T>
{
   public InstanceContext Context
   {get;private set;}

   public InstanceContext(T callbackInstance)
   {
      Context = new InstanceContext(callbackInstance);
   }
   public void ReleaseServiceInstance( )
   {
      Context.ReleaseServiceInstance( );
   }
public T ServiceInstance
   {
      get
      {
         return (T)Context.GetServiceInstance( );
      }
   }
}

By using generics, you also provide type-safe access to the hosted callback object and capture the desired callback type.

Next, define a new type-safe, generic subclass of DuplexClientBase<T>, as shown in Example 5-11.

Example 5-11. The DuplexClientBase<T,C>class

//T is the service contract and C is the callback contract
public abstract class DuplexClientBase<T,C> : DuplexClientBase<T> where T : class
{
   protected DuplexClientBase(InstanceContext<C> context) : base(context.Context)
   {}
   protected DuplexClientBase(InstanceContext<C> context,string endpointName) :
                                                 base(context.Context,endpointName)
   {}
   protected DuplexClientBase(InstanceContext<C> context,Binding binding,
                              EndpointAddress remoteAddress) :
                                        base(context.Context,binding,remoteAddress)
   {}
   protected DuplexClientBase(C callback) : base(callback)
   {}
   protected DuplexClientBase(C callback,string endpointName) :
                                                        base(callback,endpointName)
   {}
   protected DuplexClientBase(C callback,Binding binding,
                              EndpointAddress remoteAddress) :
                                               base(callback,binding,remoteAddress)
   {}

   /* More constructors */

   static DuplexClientBase( )
   {
      VerifyCallback( );
   }
   internal static void VerifyCallback( )
   {
      Type contractType = typeof(T);
      Type callbackType = typeof(C);

      object[] attributes = contractType.GetCustomAttributes(
                                           typeof(ServiceContractAttribute),false);
if(attributes.Length != 1)
      {
         throw new InvalidOperationException("Type of " + contractType +
                                             " is not a service contract");
      }
      ServiceContractAttribute serviceContractAttribute;
      serviceContractAttribute = attributes[0] as ServiceContractAttribute;
      if(callbackType != serviceContractAttribute.CallbackContract)
      {
         throw new InvalidOperationException("Type of " + callbackType +
                    " is not configured as callback contract for " + contractType);
      }
   }
}

The DuplexClientBase<T,C> class uses two type parameters: T is used for the service contract type parameter and C is used for the callback contract type parameter. The constructors of DuplexClientBase<T,C> can accept either a raw C instance or an instance of InstanceContext<C> wrapping a C instance. These enable the compiler to ensure that only compatible contexts are used. However, C# 3.0 does not support a way to constrain a declarative relationship between T and C. The workaround is to perform a single runtime check before any use of DuplexClientBase<T,C>, and abort the use of the wrong type immediately, before any damage can be done. The trick is to place the runtime verification in the C# static constructor. The static constructor of DuplexClientBase<T,C> calls the static helper method VerifyCallback( ). VerifyCallback( ) uses reflection to first verify that T is decorated with the ServiceContract attribute. Then it verifies that it has a type set for the callback contract that is the type parameter C. If not, an exception will be thrown in the static constructor, enabling you to discover the error as soon as possible at runtime.

Tip

Performing the callback contract verification in the static constructor is a technique applicable to any constraint that you cannot enforce at compile time, yet you have some programmatic way of determining and enforcing it at runtime.

Next, you need to rework the tool-generated proxy class on the client side to derive from the type-safe DuplexClientBase<T,C> class:

class MyContractClient : DuplexClientBase<IMyContract,IMyContractCallback>,
                                                                        IMyContract
{
   public MyContractClient(InstanceContext<IMyContractCallback> context) :
                                                                      base(context)
   {}
   public MyContractClient(IMyContractCallback callback) : base(callback)
   {}

   /* Rest of the constructors */
public void DoSomething( )
   {
      Channel.DoSomething( );
   }
}

You can provide the reworked proxy either with a type-safe instance context, or with the callback instance directly:

//Client code
class MyCallback : IMyContractCallback
{...}

IMyContractCallback callback = new MyCallback( );
MyContractClient proxy1 = new MyContractClient(callback);

InstanceContext<IMyContractCallback> context =
                                new InstanceContext<IMyContractCallback>(callback);
MyContractClient proxy2 = new MyContractClient(context);

Either way, the compiler will verify that the type parameters provided to the proxy match the context type parameter or the callback instance, and the static constructor will verify the relationship between the service contract and the callback instance upon instantiation.

The Duplex Factory

Similar to the ChannelFactory<T> class, WCF also offers DuplexChannelFactory<T>, which can be used for setting up duplex proxies programmatically:

public class DuplexChannelFactory<T> : ChannelFactory<T>
{
   public DuplexChannelFactory(object callback);
   public DuplexChannelFactory(object callback,string endpointName);
   public DuplexChannelFactory(InstanceContext context,string endpointName);

   public T CreateChannel(InstanceContext context);
   public static T CreateChannel(object callback,string endpointName);
   public static T CreateChannel(InstanceContext context,string endpointName);
   public static T CreateChannel(object callback,Binding binding,
                                 EndpointAddress endpointAddress);
   public static T CreateChannel(InstanceContext context,Binding binding,
                                 EndpointAddress endpointAddress);
   //More members
}

DuplexChannelFactory<T> is used just like its base class, ChannelFactory<T>, except its constructors expect either a callback instance or a callback context. Note again the use of object for the callback instance and the lack of type safety. Example 5-12 shows the reworked DuplexChannelFactory<T,C> class, which provides both compile-time and runtime type safety (similar to the fixed-up DuplexClientBase<T> class presented in Example 5-11).

Example 5-12. The DuplexChannelFactory<T,C> class

public class DuplexChannelFactory<T,C> : DuplexChannelFactory<T> where T : class
{
   static DuplexChannelFactory( )
   {
      DuplexClientBase<T,C>.VerifyCallback( );
   }

   public static T CreateChannel(C callback,string endpointName)
   {
      return DuplexChannelFactory<T>.CreateChannel(callback,endpointName);
   }
   public static T CreateChannel(InstanceContext<C> context,string endpointName)
   {
      return DuplexChannelFactory<T>.CreateChannel(context.Context,endpointName);
   }
   public static T CreateChannel(C callback,Binding binding,
                                 EndpointAddress endpointAddress)
   {
      return DuplexChannelFactory<T>.CreateChannel(callback,binding,
                                                   endpointAddress);
   }
   public static T CreateChannel(InstanceContext<C> context,Binding binding,
                                 EndpointAddress endpointAddress)
   {
      return DuplexChannelFactory<T>.CreateChannel(context,binding,
                                                   endpointAddress);
   }
   public DuplexChannelFactory(C callback) : base(callback)
   {}
   public DuplexChannelFactory(C callback,string endpointName) :
                                                      base(callback,endpointName)
   {}
   public DuplexChannelFactory(InstanceContext<C> context,string endpointName) :
                                               base(context.Context,endpointName)
   {}
   //More constructors
}

As an example of utilizing the duplex channel factory, consider Example 5-13, which adds callback ability to the InProcFactory static helper class presented in Chapter 1.

Example 5-13. Adding duplex support to InProcFactory

public static class InProcFactory
{
   public static I CreateInstance<S,I,C>(C callback) where I : class
                                                     where S : class,I
   {
      InstanceContext<C> context = new InstanceContext<C>(callback);
      return CreateInstance<S,I,C>(context);
   }
public static I CreateInstance<S,I,C>(InstanceContext<C> context)
                                                           where I : class
                                                           where S : class,I
   {
      HostRecord hostRecord = GetHostRecord<S,I>( );
      return DuplexChannelFactory<I,C>.CreateChannel(context,Binding,
                                                     hostRecord.Address);
   }
   //More members
}
//Sample client
IMyContractCallback callback = new MyCallback( );

IMyContract proxy = InProcFactory.CreateInstance<MyService,IMyContract,
                                                 IMyContractCallback>(callback);
proxy.DoSomething( );
InProcFactory.CloseProxy(proxy);

Callback Contract Hierarchy

An interesting constraint on the design of callback contracts is that a service contract can designate a callback contract only if that contract is a subinterface of all callback contracts defined by the contract's own base contracts. For example, here is an invalid callback contract definition:

interface ICallbackContract1
{...}

interface ICallbackContract2
{...}

[ServiceContract(CallbackContract = typeof(ICallbackContract1))]
interface IMyBaseContract
{...}

//Invalid
[ServiceContract(CallbackContract = typeof(ICallbackContract2))]
interface IMySubContract : IMyBaseContract
{...}

IMySubContract cannot designate ICallbackContract2 as a callback contract because ICallbackContract2 is not a subinterface of ICallbackContract1, which IMyBaseContract (the base of IMySubContract) defines as its own callback contract.

The reason for this constraint is obvious: if a client passes an endpoint reference to a service implementation of IMySubContract, that callback reference must satisfy the callback type expected by IMyBaseContract. WCF verifies the callback contract hierarchy at service load time and throws an InvalidOperationException in the case of a violation.

The straightforward way to satisfy the constraint is to reflect the service contract hierarchy in the callback contract hierarchy:

interface ICallbackContract1
{...}

interface ICallbackContract2 : ICallbackContract1
{...}

[ServiceContract(CallbackContract = typeof(ICallbackContract1))]
interface IMyBaseContract
{...}

[ServiceContract(CallbackContract = typeof(ICallbackContract2))]
interface IMySubContract : IMyBaseContract
{...}

However, you can also use multiple interface inheritance by a single callback contract and avoid mimicking the service contract hierarchy:

interface ICallbackContract1
{...}
interface ICallbackContract2
{...}
interface ICallbackContract3 : ICallbackContract2,ICallbackContract1
{...}

[ServiceContract(CallbackContract = typeof(ICallbackContract1))]
interface IMyBaseContract1
{...}
[ServiceContract(CallbackContract = typeof(ICallbackContract2))]
interface IMyBaseContract2
{...}
[ServiceContract(CallbackContract = typeof(ICallbackContract3))]
interface IMySubContract : IMyBaseContract1,IMyBaseContract2
{...}

Note, also, that a service can implement its own callback contract:

[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
interface IMyContract
{...}
[ServiceContract]
interface IMyContractCallback
{...}
class MyService : IMyContract,IMyContractCallback
{...}

The service can even store a reference to itself in some callback store (if it wishes to be called back as if it were a client).

Callbacks, Ports, and Channels

When you use either the TCP or the IPC binding, the callbacks enter the client on the outgoing channel the binding maintains to the service. There is no need to open a new port or a pipe for the callbacks. When you use the WSDualHttpBinding, WCF maintains a separate, dedicated HTTP channel for the callbacks, because HTTP itself is a unidirectional protocol. WCF selects port 80 by default for that callback channel, and it passes the service a callback address that uses HTTP, the client machine name, and port 80.

While using port 80 makes sense for Internet-based services, it is of little value to intranet-based services. In addition, if the client machine happens to also have IIS 5 or 6 running, port 80 will already be reserved, and the client will not be able to host the callback endpoint. (IIS7, by default, will allow sharing the port.) While the likelihood of an intranet application being forced to use the WSDualHttpBinding is somewhat low, it is quite common for developers who develop Internet-based applications to have IIS installed on their machines and have the callback port therefore conflict with IIS during testing and debugging.

Assigning a callback address

Fortunately, the WSDualHttpBinding offers the ClientBaseAddress property, which you can use to configure a different callback address on the client:

public class WSDualHttpBinding : Binding,...
{
   public Uri ClientBaseAddress
   {get;set;}
   //More members
}

For example, here is how to configure a base address in the client's config file:

<system.serviceModel>
   <client>
      <endpoint
         address  = "http://localhost:8008/MyService"
         binding  = "wsDualHttpBinding"
         bindingConfiguration = "ClientCallback"
         contract = "IMyContract"
      />
   </client>
   <bindings>
      <wsDualHttpBinding>
         <binding name = "ClientCallback"
            clientBaseAddress = "http://localhost:8009/"
         />
      </wsDualHttpBinding>
   </bindings>
</system.serviceModel>

The problem with using a config file to set the callback base address is that it precludes running multiple instances of the client on the same machine, which is something you are likely to do in any decent testing scenario. However, since the callback port need not be known to the service in advance, in actuality any available port will do. It is therefore better to set the client base address programmatically to any available port. You can automate this using the extension method SetClientBaseAddress( ) of my WsDualProxyHelper static helper class, shown in Example 5-14.

Example 5-14. The WsDualProxyHelper class

public static class WsDualProxyHelper
{
   public static void SetClientBaseAddress<T>(this DuplexClientBase<T> proxy,
                                              int port) where T : class
   {
      WSDualHttpBinding binding = proxy.Endpoint.Binding as WSDualHttpBinding;
      Debug.Assert(binding != null);
      binding.ClientBaseAddress = new Uri("http://localhost:"+ port + "/");
   }
   public static void SetClientBaseAddress<T>(this DuplexClientBase<T> proxy)
                                                               where T : class
   {
      lock(typeof(WsDualProxyHelper))
      {
         int portNumber = FindPort( );
         SetClientBaseAddress(proxy,portNumber);
         proxy.Open( );
      }
   }
   internal static int FindPort( )
   {
      IPEndPoint endPoint = new IPEndPoint(IPAddress.Any,0);
      using(Socket socket = new Socket(AddressFamily.InterNetwork,
                                       SocketType.Stream,
                                       ProtocolType.Tcp))
      {
         socket.Bind(endPoint);
         IPEndPoint local = (IPEndPoint)socket.LocalEndPoint;
         return local.Port;
      }
   }
}

WsDualProxyHelper offers two overloaded versions of the SetClientBaseAddress( ) extension method to the DuplexClientBase<T> class. The first simply takes a proxy instance and a port number: it verifies that the proxy is using the WSDualHttpBinding, and then it sets the client base address using the provided port. The second version of SetClientBaseAddress( ) automatically selects an available port and calls the first with the available port. To avoid a race condition with other concurrent invocations of SetClientBaseAddress( ) in the same app domain, it locks on the type itself during the sequence of looking up the available port and setting the base address, and then it opens the proxy to lock in the port. Note that a race condition is still possible with other processes or app domains on the same machine.

Using WsDualProxyHelper is straightforward:

//Sample client code:
class MyClient : IMyContractCallback
{...}

IMyContractCallback callback = new MyClient( );
InstanceContext context = new InstanceContext(callback);

MyContractClient proxy = new MyContractClient(context);
proxy.SetClientBaseAddress( );

Assigning the callback address declaratively

You can even automate the process further and assign the callback port declaratively, using a custom attribute. My CallbackBaseAddressBehaviorAttribute is a contract behavior attribute that affects only callback endpoints that use the WSDualHttpBinding. The CallbackBaseAddressBehavior attribute offers a single integer property called CallbackPort:

[AttributeUsage(AttributeTargets.Class)]
public class CallbackBaseAddressBehaviorAttribute : Attribute,IEndpointBehavior
{
   public int CallbackPort
   {get;set;}
}

CallbackPort defaults to 80. If you leave it unset, applying the CallbackBaseAddressBehavior attribute will result in the default behavior for the WSDualHttpBinding, so these two definitions are equivalent:

class MyClient : IMyContractCallback
{...}

[CallbackBaseAddressBehavior]
class MyClient : IMyContractCallback
{...}

You can explicitly specify a callback port as follows:

[CallbackBaseAddressBehavior(CallbackPort = 8009)]
class MyClient : IMyContractCallback
{...}

However, if you set CallbackPort to 0, the CallbackBaseAddressBehavior attribute will automatically select any available port for the callback:

[CallbackBaseAddressBehavior(CallbackPort = 0)]
class MyClient : IMyContractCallback
{...}

Example 5-15 lists the code implementing the CallbackBaseAddressBehavior attribute.

Example 5-15. The CallbackBaseAddressBehavior attribute

[AttributeUsage(AttributeTargets.Class)]
public class CallbackBaseAddressBehaviorAttribute : Attribute,IEndpointBehavior
{
   int m_CallbackPort = 80;

   public int CallbackPort //Accesses m_CallbackPort
   {get;set;}
   void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint,
                                      BindingParameterCollection bindingParameters)
   {
      if(CallbackPort == 80)
      {
         return;
      }
      lock(typeof(WsDualProxyHelper))
      {
         if(CallbackPort == 0)
         {
            CallbackPort = WsDualProxyHelper.FindPort( );
         }
         WSDualHttpBinding binding = endpoint.Binding as WSDualHttpBinding;
         if(binding != null)
         {
            binding.ClientBaseAddress = new Uri(
                                         "http://localhost:" + CallbackPort + "/");
         }
      }
   }
   //Do-nothing methods of IEndpointBehavior
}

The CallbackBaseAddressBehavior attribute is an endpoint behavior attribute that allows you to intercept (either on the client or the service side) the configuration of the endpoint. The attribute supports the IEndpointBehavior interface:

public interface IEndpointBehavior
{
   void AddBindingParameters(ServiceEndpoint endpoint,
                             BindingParameterCollection bindingParameters);
   //More members
}

WCF calls the AddBindingParameters( ) method on the client side just before using the proxy to the service for the first time, allowing the attribute to configure the binding used for the callback. AddBindingParameters( ) checks the value of CallbackPort. If it is 80, it does nothing. If it is 0, AddBindingParameters( ) finds an available port and assigns it to CallbackPort. Then, AddBindingParameters( ) looks up the binding used to call the service. If the call is WSDualHttpBinding, AddBindingParameters( ) sets the client base address using the callback port.

Warning

With the CallbackBaseAddressBehavior attribute, a race condition is possible with another callback object grabbing the same port, even in the same app domain.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.173.238