I was reading in the book Enterprise Integration Patterns that my dev manager bought for the team,
on the Message Endpoint pattern on the way home from work a few days ago.
“A Message Endpoint can be used to send messages or receive them, but one instance does not do both”
From my experience, this wasn’t necessarily true.
So I decided to confirm my suspicion.
The following is a mix of what I read and tested out.
Callback Operations
Not all bindings support callback operations. Only bidirectional-capable bindings (TCP, IPC) 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 MSMQ bindings also don’t support duplex communication.
To support callbacks over HTTP, WCF offers the WSDualHttpBinding, which actually sets up two WS channels:
one for the calls from the client to the service and one for the calls from the service to the client.
Two way communication can be set up for MSMQ bindings by using two one way contracts.
The CompositeDuplexBindingElement is commonly used with transports, such as HTTP, that do not allow duplex communications natively.
TCP, IPC by contrast, does allow duplex communications natively and so does not require the use of this binding element
for the service to send messages back to a client.
Callbacks, Ports, and Channels
When you use either the NetTcpBinding, NetPeerTcpBinding or the NetNamedPipebinding, the callbacks enter the client on the outgoing channel the binding maintains 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, dedicated HTTP channel for the callbacks,
because HTTP itself is a unidirectional protocol.
WCF auto-generates a ClientBaseAddress if one is not explicitly set by the user.
WCF selects port 80 by default for that callback channel,
and it 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 5 or 6 running, port 80 will already be reserved,
and the client will not be able to host the callback endpoint.
(IIS7, by default, will allow sharing the port.)
Assigning a callback address
Fortunately, the WSDualHttpBinding offers the ClientBaseAddress property,
which you can use to configure a different callback address on the client.
public class WSDualHttpBinding : Binding,... { public Uri ClientBaseAddress {get;set;} //More members }
and configuring the clients base address in the clients config file.
<system.serviceModel> <client> <endpoint address = "http://localhost:8008/MyService" binding = "wsDualHttpBinding" bindingConfiguration = "ClientCallback" contract = "IMyContract" /> </client> <bindings> <wsDualHttpBinding> <binding name = "ClientCallback" clientBaseAddress = "http://localhost:8009/" /> </wsDualHttpBinding> </bindings> </system.serviceModel>
MSDN CompositeDuplexBindingElement states
The client must expose an address at which the service can contact it to establish a connection from the service to the client.
This client address is provided by the ClientBaseAddress property.
WSDualHttpBinding.ClientBaseAddress uses the CompositeDuplexBindingElement’s ClientBaseAddress property.
x
x
As Juval Lowey states
The problem with using a config file to set the callback base address is that it precludes running multiple instances of the client on the same machine, which is something you are likely to do in any decent testing scenario. 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.
This can be automated by using one of the SetClientBaseAddress extension methods on the WsDualProxyHelper class.
public static class WsDualProxyHelper { public static void SetClientBaseAddress<T>(this 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>(this 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; } } }
There’s also a good description of how to implement callbacks in WCF in the MSDN Duplex Services item.
I’ve also implemented something similar in a project I worked on last year.
It used a singleton service, and has about 30 clients hitting it at any one point in time.