The MSMQ binding is designed to be employed on the intranet. It cannot go through firewalls by default, and more importantly, it uses a Microsoft-specific encoding and message format. Even if you could tunnel though the firewall, you would need the other party to use WCF. While requiring WCF at both ends is a reasonable assumption on the intranet, it is unrealistic to demand that from Internet-facing clients and services, and it violates a core service-oriented principle that service boundaries are explicit and that the implementation technology used by the service is immaterial to its clients. That said, Internet services may benefit from queued calls just like intranet clients and services, and yet the lack of an industry standard for such queued interoperability (and the lack of support in WCF) prevents such interaction. The solution to that is a technique I call the HTTP bridge. Unlike most of my other techniques shown in this book, the HTTP bridge is a configuration pattern rather than a set of helper classes in a small framework. The HTTP bridge, as its name implies, is designed to provide queued calls support when going over the Internet to a service or a client. The bridge requires the use of WSHttpBinding
because it is a transactional binding. The bridge has two parts to it. The bridge enables WCF clients to queue up calls to an Internet service that uses the WS binding. The bridge also enables a WCF service that exposes an HTTP endpoint over WS binding to queue up calls from its Internet clients. You can use each part of the bridge separately, or you can use them in conjunction. The bridge can only be used if the remote service contract can be queued (that is, the contract has only one-way operations), but that is usually the case; otherwise the client would not have been interested in the bridge in the first place.
Since you cannot really queue up calls with the WS binding, you would facilitate that instead using an intermediary bridging client and service. When the client wishes to queue up a call against an Internet-based service, the client would queue up a call against a local (that is, intranet-based) queued service called MyClientHttpBridge
. The client-side queued bridge service in its processing of the queued call will use the WS binding to call the remote Internet-based service. When an Internet-based service wishes to receive queued calls, it will use a queue. Because that queue cannot be accessed by non-WCF clients over the Internet, the service will use a façade—a dedicated connected service called MyServiceHttpBridge
that exposes a WS-binding endpoint. In its processing of the Internet call, MyServiceHttpBridge
simply makes a queued call against the local service. Figure 9-15 shows the HTTP bridge architecture.
It is important to use transactions between MyClientHttpBridge
, the client side of the bridge, and the remote service, and it is important to configure the service-side bridge (MyServiceHttpBridge
) to use the Client transaction mode of Chapter 7. The rationale is that by using a single transaction from the playback of the client call to the MyClientHttpBridge
to the MyServiceHttpBridge
(if present) you will approximate the transactional delivery semantic of a normal queued call, as shown in Figure 9-16.
Compare Figure 9-16 with Figure 9-6. If the delivery transaction in the bridge aborts for whatever reason, the message will roll back to the MyClientHttpBridge
queue for another retry. To maximize the chances for successful delivery, you should also turn on reliability for the call between the MyClientHttpBridge
and the remote service.
MyServiceHttpBridge
converts a regular connected call over the WS binding into a queued call and posts it to the service queue. MyServiceHttpBridge
implements a similar, but not identical, contract to the queued service. The reason is that the service-side bridge should be able to participate in the incoming transaction, but transactions cannot flow over one-way operations. The solution is to modify the contract to support and even mandate transactions. For example, if this is the original service contract:
[ServiceContract]
public interface IMyContract
{
[OperationContract(IsOneWay = true
)]
void MyMethod( );
}
then MyServiceHttpBridge
should expose this contract instead:
[ServiceContract] public interface IMyContractHttpBridge
{ [OperationContract] [TransactionFlow(TransactionFlowOption.Mandatory
)] void MyMethod( ); }
In essence, you need to set IsOneWay
to false
and use TransactionFlowOption.Mandatory
. For readability’s sake, I recommend you also rename the interface by suffixing HttpBridge
to it. The MyServiceHttpBridge
can be hosted anywhere in the service’s intranet, including the service’s own process. Example 9-32 shows the required configuration of the service and its HTTP bridge.
Example 9-32. Service-side configuration of the HTTP bridge
////////////////////// MyService Config File ////////////////////////// <services> <service name = "MyService"> <endpoint address = "net.msmq://localhost/private/MyServiceQueue" binding = "netMsmqBinding" contract = "IMyContract" /> </service> </services> ////////////////////// MyServiceHttpBridge Config File //////////////// <services> <service name = "MyServiceHttpBridge"> <endpoint address = "http://localhost:8001/MyServiceHttpBridge" binding = "wsHttpBinding" bindingConfiguration = "ReliableTransactedHTTP" contract = "IMyContractHttpBridge" /> </service> </services> <client> <endpoint address = "net.msmq://localhost/private/MyServiceQueue" binding = "netMsmqBinding" contract = "IMyContract" /> </client> <bindings> <wsHttpBinding> <binding name = "ReliableTransactedHTTP" transactionFlow = "true"> <reliableSession enabled = "true"/> </binding> </wsHttpBinding> </bindings>
The service MyService
exposes a simple queued endpoint with IMyContract
. The service MyServiceHttpBridge
exposes an endpoint with WSHttpBinding
and the IMyContractHttpBridge
contract. MyServiceHttpBridge
is also a client of the queued endpoint defined by the service. Example 9-33 shows the corresponding implementation. Note that MyServiceHttpBridge
is configured for Client transaction mode.
Example 9-33. Service-side implementation of the HTTP bridge
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyService : IMyContract { //This call comes in over MSMQ [OperationBehavior(TransactionScopeRequired = true)] public void MyMethod( ) {...} } [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyServiceHttpBridge : IMyContractHttpBridge { //This call comes in over HTTP [OperationBehavior(TransactionScopeRequired = true)] public void MyMethod( ) { MyContractClient proxy = new MyContractClient( ); //This call goes out over MSMQ proxy.MyMethod( ); proxy.Close( ); } }
The client uses queued calls against the local MyClientHttpBridge
service. The MyClientHttpBridge
can even be hosted in the same process as the client, or it can be on a separate machine on the client’s intranet. MyClientHttpBridge
uses WSHttpBinding
to call the remote service. The client needs to retrieve the metadata of the remote Internet service (such as the definition of IMyContractHttpBridge
) and convert it to a queued contract (such as IMyContract
). Example 9-34 shows the required configuration of the client and its HTTP bridge.
Example 9-34. Client-side configuration of the HTTP bridge
//////////////////////// Client Config File ////////////////////////// <client> <endpoint address = "net.msmq://localhost/private/MyClientHttpBridgeQueue" binding = "netMsmqBinding" contract = "IMyContract" /> </client> ////////////////////// MyClientHttpBridge Config File //////////////// <services> <service name = "MyClientHttpBridge"> <endpoint address = "net.msmq://localhost/private/MyClientHttpBridgeQueue" binding = "netMsmqBinding" contract = "IMyContract" /> </service> </services> <client> <endpoint address = "http://localhost:8001/MyServiceHttpBridge" binding = "wsHttpBinding" bindingConfiguration = "ReliableTransactedHTTP" contract = "IMyContractHttpBridge" /> </client> <bindings> <wsHttpBinding> <binding name = "ReliableTransactedHTTP" transactionFlow = "true"> <reliableSession enabled = "true"/> </binding> </wsHttpBinding> </bindings>
MyClientHttpBridge
exposes a simple queued endpoint with IMyContract
. MyClientHttpBridge
is also a client of the connected WS-binding endpoint defined by the service. Example 9-35 shows the corresponding implementation.
Example 9-35. Client-side implementation of the HTTP bridge
MyContractClient proxy = new MyContractClient( ); //This call goes out over MSMQ proxy.MyMethod( ); proxy.Close( ); //////////////// Client-side bridge implementation //////////// [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyClientHttpBridge : IMyContract { //This call comes in over MSQM [OperationBehavior(TransactionScopeRequired = true)] public void MyMethod( ) { MyContractHttpBridgeClient proxy = new MyContractHttpBridgeClient( ); //This call goes out over HTTP proxy.MyMethod( ); proxy.Close( ); } }