We are diving deeper into the topics of Callbacks in this post.
What this post will cover:

  • Why do you want callbacks
  • What is a Callback
  • How to deal with callbacks in WCF?
    • How can you setup a callback
    • How can a callback be invoked (service side invocation)
    • How you manage callbacks with Connect/Disconnect
    • How to use DuplexChannel/DuplexProxy for easier callback utilization

What this post will not cover about callbacks:

  • callbacks & transactions
  • callbacks & concurrency reentrancy
  • callbacks & error handling
  • callbacks & discovery

What is a callback essentially?

You might have heard the term callback in classic use in programming. You would supply a function that is called upon a certain event that takes place. The Observer Pattern or .Net delegates/events are implementations of this idea. Another important aspect of a callback is, that it reverses the client/server relationship. Is the callback invoked, the former server is the client of the callback and the former client is now the server. This is also true for WCF callbacks.

public static class MyCallbackDemonstrator
{
static void Act(Action callback, string input)
{
callback();
}
public static event Action SomethingHappend;
}
public static class MySubscriber
{
// sub in static constructor
static MySubscriber()
{
MyCallbackDemonstrator.SomethingHappend += 
OnSomethingHappened_Callback;
} 
void OnSomethingHappened_Callback()
{
Console.WriteLine("oh boy something happened");
}
}

In the example above you can see two of those implementations in action. This behavior can be replicated in WCF. We will explore how WCF implements this, but first lets take a look at why do you

Why do you even want callbacks?

Callbacks can decouple your classes from one another (not in the above example though). It also helps to avoid dependencies between your code, makes it easier to test and allows for a lot of flexibility. It also can lead to complex code when used without care and in the wrong boundaries.

In WCF you would want callbacks, because you can sort of replicate events on the service this way. Also you could notify the clients that something on the service side happened, or even that another client did something.

How are callbacks implemented in WCF?

So that the service can call the client instead of the other way around raises two questions. First of all where is the endpoint that the client provides to the service? And secondly how does the client facilitate the callback object?

When you think about this, you will learn that the callback can only be facilitated via the current operationcontext of the client service interaction. This in turn makes clear that this way of operations can only be used in WCF-WCF communications and therefore only be used with TCP, IPC and WSDualBinding. Whereas the last one uses two http connections, one for the service call and one for the callback.

This is because the non WCF-WCF communication channels/Bindings do not allow for this kind of behavior. You need a duplex capable protocol, which http is not because it is stateless in nature.

The CallbackContract and the ServiceContract

A CallbackContract is part of the used ServiceContract. I showed the properties of the ServiceContractAttribute, where you saw the property CallbackContract with the datatype System.Type. This tells us two things about the supplying of a callback. Firstly we need to supply the Type of our callbackContract to the Servicecontract. And secondly for each ServiceContract there can be only one CallbackContract.

So to set up a Callback you would need a ServiceContract, and a CallbackContract of the supplied Type like so:

[ServiceContract(Name="myservice", Namespace="callbacks", CallbackContract=typeof(IMyCallback)]
public interface IMyService
{
[OperationContract]
void MyMethod();
}
public interface IMyCallback
{
[OperationContract]
void OnCallback();
}

Note that you do not need to supply a ServiceContractAttribute on the Callback, yet you need the OperationContractAttribute on each possible callbackmethod.

Setup for callbacks on the Client side

To setup the callback on the clientSide you will need to provide the current InstanceContext with an instance of the IMyCallback implementation.

public sealed class InstanceContext : ICommunicationObject
{
public InstanceContext(object implementation);
//other members
}
class MyCallback : ICallback
{
public void OnCallback()
{}
}
// anywhere in your client:
var callback = new MyCallback();
var context = new InstanceContext(callback);

(For more information on the InstanceContext see WCF Context and Channels)
The callback methods are obviously complete WCF operations with all the attached behavior like serialization, interception messaging, contexts and channels. (how else would they get back to the service)

Instead of using the InstanceContext you could make use of DuplexProxies or DuplexChannels.
To make use of this, you need to derive from DuplexClientBase<T>:

public interface IDuplexContextChannel : IContextChannel
{
InstanceContext CallbackInstance{get;set;}
}
public abstract class DuplexClientBase<TChannel> : ClientBase<TChannel> where TChannel : class
{
protected DuplexClientBase(object callbackInstance);
protected DuplexClientBase(InstanceContext callbackInstance);
protected DuplexClientBase(object callbackInstance, string endpointConfigurationName);
protected DuplexClientBase(object callbackInstance, ServiceEndpoint endpoint);
protected DuplexClientBase(InstanceContext callbackInstance, string endpointConfigurationName);
protected DuplexClientBase(InstanceContext callbackInstance, ServiceEndpoint endpoint);
protected DuplexClientBase(object callbackInstance, string endpointConfigurationName, string remoteAddress);
protected DuplexClientBase(object callbackInstance, string endpointConfigurationName, EndpointAddress remoteAddress);
protected DuplexClientBase(object callbackInstance, Binding binding, EndpointAddress remoteAddress);
protected DuplexClientBase(InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress);
protected DuplexClientBase(InstanceContext callbackInstance, string endpointConfigurationName, EndpointAddress remoteAddress);
protected DuplexClientBase(InstanceContext callbackInstance, Binding binding, EndpointAddress remoteAddress);
public IDuplexContextChannel InnerDuplexChannel { get; }
}

The constructor of your derived DuplexClientBase needs the Instancecontext that is hosting the callback object. The client-proxy will then create an endpoint from this context by inferring the details from the service contract. The Binding that is used is the same one of the service, whereas the address is the machine name on which the InstanceContext is located. Instead of providing the InstanceContext there is a constructor that supports the use of the callback object directly.

An example for the use of DuplexClientBase<T> is the following:

class MyDuplexClient : IDuplexClientBase<IMyService>, IMyService
{
public MyDuplexClient(InstanceContext callback) : base (callback) 
{ }
public MyDuplexClient(object callbackObject) : base (callbackObject)
{ }
public void MyMethod()
{
Channel.MyMethod();
}
}
// somewhere to invoke the proxy
var callback = new MyCallback();
var context = new InstanceContext(callback);
var proxy = new MyContractClient(context);
proxy.MyMethod();

It is important to note, that the client cannot close the proxy while awaiting callbacks! Else on the service side you would get an error.

DuplexChannelFactory

Another method to wire up the callback contract with the service is to use the DuplexChannelFactory<T> that derives from the ChannelFactory<T>.
This lets you set up your proxy programmatically which is more explicit, and therefore much better (in my opinion) for clarity.

public class DuplexChannelFactory<T> : ChannelFactory<T>
{
public DuplexChannelFactory(object callback);
public DuplexChannelFactory(object callback, 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
}

The DuplexChannelFactory<T> is used similiar to the ChannelFactory<T>. The only difference is, that you supply a InstanceContext or a callback Object. The following example shows a use of a DuplexChannelFactory.

var instanceContext = new InstanceContext(new MyCallback());
var channelFactory = new DuplexChannelFactory<IMyService>(instanceContext);
var proxy = channelFactory.CreateChannel();
proxy.MyMethod();
// do not close as long as you await callbacks.

Or you could use the static CreateChannel method:

var binding = new NetTcpBinding();
var address = "net.tcp://localhost:8080/MyService";
var callback = new MyCallback();
var proxy = DuplexChannelFactory<IMyService>
.CreateChannel(callback, binding, address);
proxy.MyMethod();

Later in this post we are going to tackle the issue with how the client can close the proxy without disturbing the service that might want to send callbacks.

Service side Callback invocation

So now where we learned to prepare the client proxy for callbacks it is about time to explore how the callback can be invoked on the service side.

The endpoint reference of the client is passed to the service with each call, wrapped in the context (hence the instance context provided to the proxy). To get hold of this context, you can gain access to the callback channel via the OperationContext’s generic GetCallbackChannel<T> method.

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

With the reference you can do as you please, store it for later use, invoke it during the context and so on and so forth. Yet it is not allowed to invoke a callback during a service operation, when you use the default ConcurrencyMode. Which is ConcurrencyMode.Single.

You need to configure your service to avoid deadlocks on a callback. The deadlocks ensue because of the default concurrency mode. During a service call the service is locked for the thread and the given context. To invoke the callback would require to gain access to the same lock in the same context. This would result in a deadlock, because the client would wait on the service call to finish whereas the service calling the client would wait on the client that calls the service.
I will go deeper into Concurrency in a post about asynchronous WCF, for now you only need to know, that there are three solutions to this.

  1. Configure your ServiceContract for ConcurrencyMode.Multiple. This will increase the complexity of the service, and is therefor the least desirable solution. (You need to synchronize the instance access on the service side etc.)
  2. Configure your ServiceContract for ConcurrencyMode.Reentrant. This avoids the exact situation described above, by silently releasing the lock for the thread, when a client context is given, that was already in a context with the service call.
  3. Make all your callback operations as one way operations, by applying the flag: IsOneWay = true on each OperationContractAttribute.

I personally like the third solution best, because it goes hand in hand with the idea of notification/event publishing.

Manage Callback connections

Above we saw, that the proxy cannot be closed without the danger of an exception at the service side. We could wrap the callback call in an try/catch/finally block, yet this would not clearly state our intent.

So I am going to show you a way to manage the connection to a client in a safe manner. The essential idea is to write a service contract that supplies a connect, a disconnect method and the actual service implementation. So the client would call the Connect method, call the service operations, wait on callbacks and disconnect, or disconnect before you get a callback.

The technical details will be shown in the following example:

[ServiceContract(CallbackContract= typeof(IMyCallback), Name="connectingservice", Namespace="callbacks")]
public interface IMyConnectionService
{
[OperationContract(IsInitiating=false)]
void MyMethod_1();
[OperationContract(IsInitating=false)]
void MyMethod_2();
[OperationContract(IsInitiating=true)]
void Connect();
[OperationContract(IsTerminating=true)]
void Disconnect();
}

(for the topics discussed here, you might want to refresh on WCF Instancemanagement)
As you can see, the actual service methods are decorated with the IsInitiating flag that is initialized with false. This means a proxy that would call this methods would throw an exception, because you’ll need to invoke the method with IsInitiating=true first. If all methods are decorated with IsInitiating or IsTerminating, you could leave out the IsInitiating=true, yet as I state often, explicit is better than implicit. So you can identify the connect method by looking at the IsInitiating=true.
The Disconnect method has the IsTerminating flag set to true. This means that the channel and session to the service are getting terminated with the call to this method.

During the connect method, you will want to store the callback objects from incoming clients. This can be done in a static List or a reference that is initiated elsewhere and is injected into the service. This depends on the given Instancemanagement your service uses, because this determines how the service is initiated and managed. For simplicities sake I will use a static list that stores all the callback references. On Connect they are added to the List, and removed on Disconnect. You can get the callback from the OperationContext.Current.CallbackChannel<T>() method.

public class ServiceImplementation : IMyConnectionService
{
public void MyMethod_1(){ /* omitted for simplicities sake*/ }
public void MyMethod_2(){ /* omitted for simplicities sake*/ }
private static List<IMyCallback> s_connections = new List<IMyCallback>();
public void Connect()
{
var callback = OperationContext.Current.GetCallbackChannel<IMyCallback>();
if (!s_connections.Contains(callback))
{ 
s_connections.Add(callback);
}
}
public void Disconnect()
{
var callback = OperationContext.Current.GetCallbackChannel<IMyCallback>();
if (s_connections.Contains(callback))
{
s_connections.Remove(callback); 
}
}
public static void InvokeCallback()
{
Action<IMyCallback> action = callback => callback.OnCallback();
s_connections.ForEach(action);
}
}

And on the client side, note that I added the IsOneWay flag.

public interface IMyCallback
{
[OperationContract(IsOneWay=true)]
void OnCallback();
}
public class MyCallback : IMyCallback
{
public void OnCallback()
{
// do something on notification
}
}

The Disconnect method is especially needed for per call services, else with the end of the context, the service would pile up lots of dead references for callback clients in the list. A similiar need exists for a singleton Service, because it has no defined end and will therefore pile up lots of different dead references. For a per session service this might be avoided if you store the reference in some kind of member variable. Because when the session terminates, it disposes of all references and they will be garbage collected.

Summary

In this post we explored how to set up a callback mechanism in WCF to emulate the classic programming model of callbacks/observers and events.
We revisited what a callback is and how the are implemented in WCF.

WCF only supports WCF-WCF communication for callbacks, that is TCP, IPC and WS Dual bindings. To create a Callback you need to provide your service contract with the Type of your CallbackService. For this you inject the Type into the ServiceContractAttribute of the service. Note that your Callback does not need to implement the ServiceContractAttribute, but each operation needs to implement the OperationContractAttribute.
Next we looked at the connection from client to service and how the service invokes the callback. We have also seen which limitations a callback has and how to overcome them, and in which way you can manage the connections to your callback clients.

Categories: HowTo

0 Comments

Leave a Reply

Avatar placeholder