Section 5.3. Callback Operations


5.3. Callback Operations

WCF supports allowing the 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).

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


The client also has to facilitate hosting the callback object. 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 clients that some event has happened on the service side. Not all bindings support callback operations. Only bidirectional-capable bindings can be used for callbacks. For example, because of its connectionless nature, HTTP cannot be used for callbacks, and therefore you cannot use callbacks over BasicHttpBinding or WSHttpBinding. WCF offers callback support for NetTcpBinding and NetNamedPipeBinding, because by their very nature, the TCP and the IPC protocols support duplex communication. To support callbacks over HTTP, WCF offers the WSDualHttpBinding, which actually sets up two HTTP channels: one for the calls from the client to the service and one for the calls from the service to the client.

5.3.1. 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 a ServiceContract attributethe 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.

Once the client imports the metadata of the callback contract, the imported callback interface will not have the same name as in the original service-side definition. Instead, it will be have the name of the service contract interface suffixed with the word Callback. For example, if the client imports the definitions of Example 5-1, the client would get these definitions instead:

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

For simplicity's sake, I recommend even on the service side to name the callback contract as the service contract interface suffixed by Callback.


5.3.2. 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 to 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.

5.3.2.1. Duplex proxy

Whenever you're 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 endpointConfigurationName);    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 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, the DuplexClientBase<T> also offers the InnerDuplexChannel property of the type IduplexContextChannel, which offers the context via the CallbackInstance property.

When using SvcUtil or Visual Studio 2005 to generate a proxy class targeting a service with a callback contract, the tools will generate a class that derives from DuplexClientBase<T> as shown in Example 5-3.

Example 5-3. Tool-generated duplex proxy

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

Interestingly enough, 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

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

5.3.3. Service-Side Callback Invocation

The client-side callback endpoint reference is passed along with every call the client makes to the service, and 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<ISomeCallbackContract> m_Callbacks =                                                 new List<ISomeCallbackContract>( );    public void DoSomething( )    {       ISomeCallbackContract callback = OperationContext.Current.                                        GetCallbackChannel<ISomeCallbackContract>( );       if(m_Callbacks.Contains(callback) == false)       {          m_Callbacks.Add(callback);       }    }    public static void CallClients( )    {     Action<ISomeCallbackContract> invoke = delegate(ISomeCallbackContract callback)                                            {                                               callback.OnCallback( );                                            };       m_Callbacks.ForEach(invoke);    } } 

Using the same definitions as Example 5-1, the service uses a static, generic linked list to store references to interfaces of the type ISomeCallbackContract. 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 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( ). Any party on the host side can simply use that to call back to the clients:

 MyService.CallClients( ); 

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

Example 5-6 (and similar examples in this chapter) does not synchronize access to the callbacks 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.


5.3.3.1. Callback reentrancy

The service may also want to invoke the callback reference 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 is associated with a lock, and only one thread at a time can own the lock and access the service instance. 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 once the callback returns requires ownership of the same lock, and so a deadlock would occur. Note that the service may still invoke callbacks to other clients or call other services. It is the callback to the calling client that will cause the deadlock.

To avoid 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, which would not associate it with a lock and would therefore allow the callback, but would also 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 is still associated with a lock and only a 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, if your service needs to call back to its clients, configure 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 reaches to 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. Configure 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 have the callback contract operations configured as one-way operations. Doing so will enable the service to call back even when concurrency 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( );    } } 

5.3.4. 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 life cycle of the connection. As mentioned previously, the service can only call back to the client if the client-side channel is still open, typically done 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 with it the callback reference, in the Disconnect( ) method the service can remove the callback reference from its internal store.

In addition, for symmetry's sake, I recommend also 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 = delegate(IMyContractCallback callback)                                          {                                             callback.OnCallback( );                                          };       m_Callbacks.ForEach(invoke);    }    public void DoSomething( )    {...} } 

5.3.4.1. Connection management and instance mode

A per-call service can use the 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 examples so far. The reason is clear: any instance state the service may use to store the reference will be gone when the operation returns. As such, using a Disconnect( )-like method is especially required by a per-call service. A similar need exists with a singleton service. The singleton lifetime has no end, and as such it will accumulate an open number of callback references, and as time goes by most of them will become stale as the callback clients are no longer there. 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 at all, 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 sessionit 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( ).

Finally, you may 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.

5.3.5. 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, even 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 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 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 instantiate 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> {    InstanceContext m_InstanceContext;    public InstanceContext(T implementation)    {       m_InstanceContext = new InstanceContext(implementation);    }    public InstanceContext Context    {       get       {          return m_InstanceContext;       }    }    public T ServiceInstance    {       get       {          return (T)m_InstanceContext.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. TheDuplexClientBase<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# 2.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 could take place. 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( ). What VerifyCallback( ) does is to use 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. By throwing an exception in the static constructor, you will discover the error as soon as possible at runtime.

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 machine-generated proxy class on the client side to derive from the type-safe DuplexClientBase<T,C> class:

 partial 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 either provide the reworked proxy with a type-safe instance context or with the callback instance directly:

 //Client code class MyClient : IMyContractCallback {...} IMyContractCallback callback = new MyClient( ); 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.

5.3.6. 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. Similar to fixing up the DuplexClientBase<T> class, Example 5-12 shows the reworked DuplexChannelFactory<T,C> class, which provides both compile-time and runtime type safety.

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 for 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,NamedPipeBinding,new EndpointAddress(hostRecord.Address));    }    //More members } //Sample client IMyContractCallback callback = new MyClient( ); IMyContract proxy = InProcFactory.CreateInstance                              <MyService,IMyContract,IMyContractCallback>(callback); proxy.DoSomething( ); InProcFactory.CloseProxy(proxy); 

5.3.7. Callback Contract Hierarchy

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

 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, while IMyBaseContract (the base of IMySubContract) defines ICallbackContract1 as its own callback contract. The reason for the 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 the 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 wished to be called back as if it were a client).

5.3.8. Callback, Ports, and Channels

When you use either the NetTcpBinding or the NetNamedPipeBinding, the callbacks enter the client on the outgoing channel maintained by the binding 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 HTTP channel dedicated for the callbacks, because HTTP itself is a unidirectional protocol. For that callback channel, WCF selects port 80 by default and 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 running, port 80 will be reserved already, and the client will not be able to host the callback endpoint. While the likelihood of an intranet application being forced to use 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.

5.3.8.1. Assigning a callback address

Fortunately, the WSDualHttpBinding binding offers the ClientBaseAddress property, where you can configure on the client a different callback URI:

 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 = "ClienCallback"          contract = "IMyContract"       />    </client>    <bindings>       <wsDualHttpBinding>          <binding name = "ClientCallback"             clientBaseAddress = "http://localhost:8009/"          />       </wsDualHttpBinding>    </bindings> </system.serviceModel> 

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 WsDualProxyHelper static helper class shown in Example 5-14.

Example 5-14. The WsDualProxyHelper class

 public static class WsDualProxyHelper {    public static void SetClientBaseAddress<T>(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>(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( ) method. The first simply takes a proxy instance and port number. It verifies the proxy is using the WSDualHttpBinding binding, 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 one 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); WsDualProxyHelper.SetClientBaseAddress(proxy); 

Another advantage of programmatically setting the callback address (as opposed to hardcoding it in the config file) is that it supports launching a few clients on the same machine during testing.

5.3.8.2. Assigning callback address declaratively

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

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

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

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

You can explicitly specify a callback port:

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

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

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

Example 5-15 lists the code of CallbackBaseAddressBehaviorAttribute.

Example 5-15. The CallbackBaseAddressBehaviorAttribute

 [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 } 

CallbackBaseAddressBehaviorAttribute is an endpoint behavior attribute allowing you to intercept (either on the client or service side) the configuration of the endpoint. The attribute supports the IContractBehavior 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.

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





Programming WCF Services
Programming WCF Services
ISBN: 0596526997
EAN: 2147483647
Year: 2004
Pages: 148
Authors: Juval Lowy

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