Introduction
This article is intended to illustrate how to implement callback operations in Windows Communication Foundation through a common business scenario where the service needs to notify that some events has happened to the client. During a callback, in many aspects the tables are turned: the service becomes the client, and the client becomes the server. So, we need to develop an interesting application supporting the solution.
Implementing the solution
The first thing to know before implementing this approach is that not all bindings support callback operations and only bidirectional-capable bindings can be used. Due to the connectionless nature of HTTP, this transport protocol cannot be used for callbacks, that's, you cannot use the BasicHttpBinding and WSHttpBinding for this purpose. In order to support callbacks in your application, WCF provides the WSDualHttpBinding, which actually sets up two HTTP channels: one for the calls from the client to the server, and one for the calls from the server to the client.
Now, let's create a console application to host the service in Visual Studio.NET and add a reference to the System.ServiceModel assembly.
Defining the callback contract
The callback operations are part of the service contract. A service contract can have at most one callback contract. Once defined, the clients are required to support the callback and also provide the callback endpoint to service in every call.
In our example, we have a service which provides a math service doing some long term calculation. We want to be notified when the calculation operation begins. The service contract attribute annotates our contract and offers a CallbackContract property of the type Type setting up our callback contract, as shown in Listing 1.
Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.ServiceModel
Namespace CallbackApp
Public Interface IMathCalculationCallback
<OperationContract()> _
Sub OnCalculating()
End Interface
<ServiceContract(CallbackContract:=GetType(IMathCalculationCallback))> _
Public Interface IMathCalculation
<OperationContract()> _
Sub DoLongCalculation(ByVal nParam1 As Integer, ByVal nParam2 As Integer)
End Interface
End Namespace
Listing 1. Definition of the service contract and the associated callback contract
The service implementation
In order to invoke the client callback from the service, we need a reference to the callback object. When the client invokes the service operations, it supplies a callback channel for the communication with the server through the callback. This channel can be referenced from the server by calling the GetCallbackChannel operation on the global OperationContext instance, as shown in the Listing 2. The DoLongCalculation operation does some long initialization, then notifies to the client the a complex operation begins now (in our case this complex operation is nParam1 + nParam2, I guess it is joke but with a delay System.Threading.Thread.Sleep(10000)).
<ServiceBehavior(ConcurrencyMode:=ConcurrencyMode.Reentrant)> _
Public Class MathCalculationService
Implements IMathCalculation
Public Function DoLongCalculation(ByVal nParam1 As Integer, ByVal nParam2 As Integer) As Integer
System.Threading.Thread.Sleep(10000)
Dim objCallback As IMathCalculationCallback = OperationContext.Current.GetCallbackChannel(Of IMathCalculationCallback)()
If objCallback IsNot Nothing Then
objCallback.OnCalculating()
End If
System.Threading.Thread.Sleep(10000)
Return nParam1 + nParam2
End Function
End Class
Listing 2. The MathCalculationService implementation
You may notice that the service is invoking the callback reference while executing the operation DoLongCalculation. By default the service class is configure for single-threaded access, thus the service is associated with a lock, and only one thread at a time can own the lock and access the service instance. When the service invokes the callback reference while executing one of its operations, the service thread is blocked, because the thread which is processing the reply message from the client once the callback returns a message response requires ownership of the same lock, so a deadlock situation occurs. To avoid this situation, there are three possibilities:
- Configure the service for multi-threaded access. The drawback is that the developer needs to provide synchronization codes.
- Configure the service for reentrancy. The service is associated with a lock, and only one thread is allowed to access the service, however if the service is calling back to its clients, WCF will release the lock first. This is the strategy that I follow in my example, and I implemented it by decorating the service class with the attribute ServiceBehavior and setting the property ConcurrencyMode to ConcurrencyMode.Reentrant as shown in Listing 2.
- Configure the operations as one-way operation because there is no reply message to contend for the lock.
The application's main workflow is shown in Listing 3.
Class Program
Private Shared Sub Main(ByVal args As String())
MathCalculationServiceHost.StartService()
System.Console.WriteLine("Please, press any key to finish ...")
System.Console.Read()
End Sub
End Class
Listing 3. The service application's main workflow
And finally the configuration file is shown in Listing 4.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="CallbackApp.MathCalculationService" behaviorConfiguration="MathCalculationServiceBeh">
<endpoint contract="CallbackApp.IMathCalculation" binding="wsDualHttpBinding"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MathCalculationServiceBeh" >
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Listing 4. The service application's configuration file
Developing the client side
Now, add another application to the solution and name it CallbackClientApp, and also add a reference to the System.ServiceModel assembly.
In order to generate the proxy class, you need to open command windows and change to the directory of the client application, and run the following line command shown in Listing 5. As you can see the proxy inherits from the class System.ServiceModel.DuplexClientBase.
svcutil
http://localhost:8080/CallbackApp/MathCalculationService.svc?wsdl Listing 5.
The generated proxy is shown in Listing 6.
'------------------------------------------------------------------------------
' <auto-generated>
' This code was generated by a tool.
' Runtime Version:2.0.50727.42
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
' </auto-generated>
'------------------------------------------------------------------------------
Namespace CallbackClientApp
<System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")> _
<System.ServiceModel.ServiceContractAttribute(ConfigurationName:="IMathCalculation", CallbackContract:=GetType(IMathCalculationCallback))> _
Public Interface IMathCalculation
<System.ServiceModel.OperationContractAttribute(Action:="http://tempuri.org/IMathCalculation/DoLongCalculation", ReplyAction:="http://tempuri.org/IMathCalculation/DoLongCalculationResponse")> _
Function DoLongCalculation(ByVal nParam1 As Integer, ByVal nParam2 As Integer) As Integer
End Interface
<System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")> _
Public Interface IMathCalculationCallback
<System.ServiceModel.OperationContractAttribute(Action:="http://tempuri.org/IMathCalculation/OnCalculating", ReplyAction:="http://tempuri.org/IMathCalculation/OnCalculatingResponse")> _
Sub OnCalculating()
End Interface
<System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")> _
Public Interface IMathCalculationChannel
Inherits IMathCalculation
Inherits System.ServiceModel.IClientChannel
End Interface
<System.Diagnostics.DebuggerStepThroughAttribute()> _
<System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")> _
Partial Public Class MathCalculationClient
Inherits System.ServiceModel.DuplexClientBase(Of IMathCalculation)
Implements IMathCalculation
Public Sub New(ByVal callbackInstance As System.ServiceModel.InstanceContext)
MyBase.New(callbackInstance)
End Sub
Public Sub New(ByVal callbackInstance As System.ServiceModel.InstanceContext, ByVal endpointConfigurationName As String)
MyBase.New(callbackInstance, endpointConfigurationName)
End Sub
Public Sub New(ByVal callbackInstance As System.ServiceModel.InstanceContext, ByVal endpointConfigurationName As String, ByVal remoteAddress As String)
MyBase.New(callbackInstance, endpointConfigurationName, remoteAddress)
End Sub
Public Sub New(ByVal callbackInstance As System.ServiceModel.InstanceContext, ByVal endpointConfigurationName As String, ByVal remoteAddress As System.ServiceModel.EndpointAddress)
MyBase.New(callbackInstance, endpointConfigurationName, remoteAddress)
End Sub
Public Sub New(ByVal callbackInstance As System.ServiceModel.InstanceContext, ByVal binding As System.ServiceModel.Channels.Binding, ByVal remoteAddress As System.ServiceModel.EndpointAddress)
MyBase.New(callbackInstance, binding, remoteAddress)
End Sub
Public Function DoLongCalculation(ByVal nParam1 As Integer, ByVal nParam2 As Integer) As Integer
Return MyBase.Channel.DoLongCalculation(nParam1, nParam2)
End Function
End Class
End Namespace
Listing 6. The generated proxy In order to use the callback capabilities the client application needs to create an instance of a class which implements the callback logic, host it in a context, create the proxy and call the service passing the callback instance's reference.
Let's define the callback class as shown in Listing 7.
Class MathCalculationCallback
Implements IMathCalculationCallback
#Region "IMathCalculationCallback Members"
Public Sub OnCalculating()
System.Console.WriteLine("The server begins to calculate, please for a moment ...")
End Sub
#End Region
End Class
Listing 7. The callback class definition
Then, we define the client's main workflow as shown in Listing 8.
Class Program
Private Shared Sub Main(ByVal args As String())
Dim objCallback As IMathCalculationCallback = New MathCalculationCallback()
Dim objContext As New InstanceContext(objCallback)
Dim nResult As Integer = 0
Using objProxy As New MathCalculationClient(objContext)
nResult = objProxy.DoLongCalculation(1, 2)
End Using
System.Console.WriteLine("The result is {0}", nResult)
System.Console.WriteLine("Press any key to finish ...")
System.Console.Read()
End Sub
End Class
Listing 8. The client's main workflow
And the configuration file is shown in Listing 9.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<bindings>
<wsDualHttpBinding>
<binding name="WSDualHttpBinding_IMathCalculation" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00" />
<security mode="Message">
<message clientCredentialType="Windows" negotiateServiceCredential="true"
algorithmSuite="Default" />
</security>
</binding>
</wsDualHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:8080/CallbackApp/MathCalculationService.svc"
binding="wsDualHttpBinding" bindingConfiguration="WSDualHttpBinding_IMathCalculation"
contract="IMathCalculation" name="WSDualHttpBinding_IMathCalculation">
</endpoint>
</client>
</system.serviceModel>
</configuration>
Listing 9. The configuration file
Let's see the client application's output in Figure 1.

Figure 1: The client application's output.
Conclusion
In this article, I covered the main concepts and strategies to develop a WCF service which notifies events to the client using callback operations, and how the client must implement the logic associated to the event handling.
NOTE: THIS ARTICLE IS CONVERTED FROM C# TO VB.NET USING A CONVERSION TOOL. ORIGINAL ARTICLE CAN BE FOUND ON C# Corner (http://www.c-sharpcorner.com/).