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. The only two commonly used bindings that offer callbacks are the NetTcpBinding and the NetNamedPipeBinding, because by their very nature, the TCP and the IPC protocols support duplex communication.

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();
}

Note

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. 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 2010 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 2010-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.

Note

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 you can set the 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 to its calling client, 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 calling 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))
      {
         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 == 0)
      {
         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, .NET 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.

Note

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, if you used Visual Studio 2010 to generate the proxy, you need to rework the proxy 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 my 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 sub-interface 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 sub-interface 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).

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

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