Duplex Services
A duplex service contract is a message exchange pattern in which both endpoints can send messages to the other independently. A duplex service, therefore, can send messages back to the client endpoint, providing event-like behavior. Duplex communication occurs when a client connects to a service and provides the service with a channel on which the service can send messages back to the client. Note that the event-like behavior of duplex services only works within a session.
To create a duplex contract you create a pair of interfaces. The first is the service contract interface that describes the operations that a client can invoke. That service contract must specify a callback contract in the System.ServiceModel.ServiceContractAttribute.CallbackContract property. The callback contract is the interface that defines the operations that the service can call on the client endpoint. A duplex contract does not require a session, although the system-provided duplex bindings make use of them.
The following is an example of a duplex contract.
<ServiceContract(Namespace := "http://Microsoft.ServiceModel.Samples", SessionMode:=SessionMode.Required, CallbackContract:=GetType(ICalculatorDuplexCallback))> _
Public Interface ICalculatorDuplex
<OperationContract(IsOneWay := True)> _
Sub Clear()
<OperationContract(IsOneWay := True)> _
Sub AddTo(ByVal n As Double)
<OperationContract(IsOneWay := True)> _
Sub SubtractFrom(ByVal n As Double)
<OperationContract(IsOneWay := True)> _
Sub MultiplyBy(ByVal n As Double)
<OperationContract(IsOneWay := True)> _
Sub DivideBy(ByVal n As Double)
End Interface
Public Interface ICalculatorDuplexCallback
<OperationContract(IsOneWay := True)> _
Sub Equals(ByVal result As Double)
<OperationContract(IsOneWay := True)> _
Sub Equation(ByVal eqn As String)
End Interface
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode=SessionMode.Required,
CallbackContract=typeof(ICalculatorDuplexCallback))]
public interface ICalculatorDuplex
{
[OperationContract(IsOneWay = true)]
void Clear();
[OperationContract(IsOneWay = true)]
void AddTo(double n);
[OperationContract(IsOneWay = true)]
void SubtractFrom(double n);
[OperationContract(IsOneWay = true)]
void MultiplyBy(double n);
[OperationContract(IsOneWay = true)]
void DivideBy(double n);
}
public interface ICalculatorDuplexCallback
{
[OperationContract(IsOneWay = true)]
void Equals(double result);
[OperationContract(IsOneWay = true)]
void Equation(string eqn);
}
The CalculatorService
class implements the primary ICalculatorDuplex
interface. The service uses the PerSession instance mode to maintain the result for each session. A private property named Callback
accesses the callback channel to the client. The service uses the callback for sending messages back to the client through the callback interface, as shown in the following sample code.
<ServiceBehavior(InstanceContextMode := InstanceContextMode.PerSession)> _
Public Class CalculatorService
Implements ICalculatorDuplex
Private result As Double = 0.0R
Private equation As String
Public Sub New()
equation = result.ToString()
End Sub
Public Sub Clear() Implements ICalculatorDuplex.Clear
Callback.Equation(equation & " = " & result.ToString())
equation = result.ToString()
End Sub
Public Sub AddTo(ByVal n As Double) Implements ICalculatorDuplex.AddTo
result += n
equation &= " + " & n.ToString()
CType(Callback, Object).Equals(result)
End Sub
Public Sub SubtractFrom(ByVal n As Double) Implements ICalculatorDuplex.SubtractFrom
result -= n
equation &= " - " & n.ToString()
CType(Callback, Object).Equals(result)
End Sub
Public Sub MultiplyBy(ByVal n As Double) Implements ICalculatorDuplex.MultiplyBy
result *= n
equation &= " * " & n.ToString()
CType(Callback, Object).Equals(result)
End Sub
Public Sub DivideBy(ByVal n As Double) Implements ICalculatorDuplex.DivideBy
result /= n
equation &= " / " & n.ToString()
CType(Callback, Object).Equals(result)
End Sub
Private ReadOnly Property Callback() As ICalculatorDuplexCallback
Get
Return OperationContext.Current.GetCallbackChannel(Of ICalculatorDuplexCallback)()
End Get
End Property
End Class
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class CalculatorService : ICalculatorDuplex
{
double result = 0.0D;
string equation;
public CalculatorService()
{
equation = result.ToString();
}
public void Clear()
{
Callback.Equation(equation + " = " + result.ToString());
equation = result.ToString();
}
public void AddTo(double n)
{
result += n;
equation += " + " + n.ToString();
Callback.Equals(result);
}
public void SubtractFrom(double n)
{
result -= n;
equation += " - " + n.ToString();
Callback.Equals(result);
}
public void MultiplyBy(double n)
{
result *= n;
equation += " * " + n.ToString();
Callback.Equals(result);
}
public void DivideBy(double n)
{
result /= n;
equation += " / " + n.ToString();
Callback.Equals(result);
}
ICalculatorDuplexCallback Callback
{
get
{
return OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>();
}
}
}
The client must provide a class that implements the callback interface of the duplex contract, for receiving messages from the service. The following sample code shows a CallbackHandler
class that implements the ICalculatorDuplexCallback
interface.
Public Class CallbackHandler
Implements ICalculatorDuplexCallback
Public Overridable Shadows Sub Equals(ByVal result As Double) Implements ICalculatorDuplexCallback.Equals
Console.WriteLine("Equals({0})", result)
End Sub
Public Sub Equation(ByVal eqn As String) Implements ICalculatorDuplexCallback.Equation
Console.WriteLine("Equation({0})", eqn)
End Sub
End Class
public class CallbackHandler : ICalculatorDuplexCallback
{
public void Equals(double result)
{
Console.WriteLine("Equals({0})", result);
}
public void Equation(string eqn)
{
Console.WriteLine("Equation({0})", eqn);
}
}
The WCF client that is generated for a duplex contract requires a InstanceContext class to be provided upon construction. This InstanceContext class is used as the site for an object that implements the callback interface and handles messages that are sent back from the service. An InstanceContext class is constructed with an instance of the CallbackHandler
class. This object handles messages sent from the service to the client on the callback interface.
' Construct InstanceContext to handle messages on callback interface
Dim instanceContext As New InstanceContext(New CallbackHandler())
' Create a client
Dim client As New CalculatorDuplexClient(instanceContext)
// Construct InstanceContext to handle messages on callback interface
InstanceContext instanceContext = new InstanceContext(new CallbackHandler());
// Create a client
CalculatorDuplexClient client = new CalculatorDuplexClient(instanceContext);
The configuration for the service must be set up to provide a binding that supports both session communication and duplex communication. The wsDualHttpBinding element supports session communication and allows duplex communication by providing dual HTTP connections, one for each direction.
On the client, you must configure an address that the server can use to connect to the client, as shown in the following sample configuration.
Note
Non-duplex clients that fail to authenticate using a secure conversation typically throw a MessageSecurityException. However, if a duplex client that uses a secure conversation fails to authenticate, the client receives a TimeoutException instead.
If you create a client/service using the WSHttpBinding element and you do not include the client callback endpoint, you will receive the following error.
HTTP could not register URL
htp://+:80/Temporary_Listen_Addresses/<guid> because TCP port 80 is being used by another application.
The following sample code shows how to specify the client endpoint address in code.
WSDualHttpBinding binding = new WSDualHttpBinding();
EndpointAddress endptadr = new EndpointAddress("https://localhost:12000/DuplexTestUsingCode/Server");
binding.ClientBaseAddress = new Uri("https://localhost:8000/DuplexTestUsingCode/Client/");
The following sample code shows how to specify the client endpoint address in configuration.
<client>
<endpoint name ="ServerEndpoint"
address="https://localhost:12000/DuplexTestUsingConfig/Server"
bindingConfiguration="WSDualHttpBinding_IDuplexTest"
binding="wsDualHttpBinding"
contract="IDuplexTest" />
</client>
<bindings>
<wsDualHttpBinding>
<binding name="WSDualHttpBinding_IDuplexTest"
clientBaseAddress="https://localhost:8000/myClient/" >
<security mode="None"/>
</binding>
</wsDualHttpBinding>
</bindings>
Caution: |
---|
The duplex model does not automatically detect when a service or client closes its channel. So if a service unexpectedly terminates, by default the service will not be notified, or if a client unexpectedly terminates, the service will not be notified. Clients and services can implement their own protocol to notify each other if they so choose. |
See Also
Tasks
Duplex
How to: Create a Channel Factory and Use it to Create and Manage Channels