5. Step 5: Implementing the Message Exchange Handlers
Now that you have learned
how to implement the metadata handlers, it's time to get to the meat
and potatoes of any WCF LOB Adapter—communicating with the target LOB
system. In this step, you will learn how to implement the synchronous
inbound and outbound handlers. In the outbound scenario, the WCF LOB
Adapters communicate with the target LOB systems by sending the request
and receiving the response that will be returned to the client. In the
inbound scenario, the WCF LOB Adapters monitor the target system for a
particular event or data and notify the client. These message exchange
patterns are represented by two interfaces: IOutboundHandler and IInboundHandler.
5.1. Implementing the IOutboundHandler Interface
To enable the outbound capabilities of the adapter, you have to implement the IOutboundHandler interface from the Microsoft.ServiceModel.Channels.CommonIOutboundHandler interface is located in the Microsoft.ServiceMode.Channels.dll assembly and is defined as follows: namespace. The
public interface IOutboundHandler : IConnectionHandler, IDisposable
{
Message Execute(Message message, TimeSpan timeout);
}
The Execute
method extracts data from the incoming WCF message, invokes the
corresponding method on the target LOB system passing the extracted data
as parameters, and returns a WCF response message to the client
application. Implementing the Execute method, you have to follow two rules:
If your adapter participates in one-way operations and doesn't expect to receive any outgoing message, the Execute method has to return null.
If your adapter participates in two-way operations with Microsoft.ServiceModel.Channels.Common.OperationResult equal to nothing, the Execute
method has to return the WCF message with an empty body. Otherwise, the
outgoing message has to contain the operation result. To be consumable
by BizTalk applications, your adapter must support two-way operations
because of the restrictions imposed by the BizTalk WCF-adapter.
As the product documentation states, the WCF LOB Adapter SDK forms incoming and outgoing messages according to the following rules.
For the incoming WCF request messages:
Message action = operation's nodeID
Message body is formed according to the following pattern:
<displayname><parametername>[value]</parametername></displayname>
For the outgoing WCF response messages:
Message action = operation's nodeID + \response
Message body takes the following form:
<displayName + "Response"><displayName + "Result">
<datatype>[value]</datatype>
</displayName + "Result"></displayName + "Response">
For example, for the string GetRooms (string) operation supported by the hotel adapter with nodeId = "Hotel\GetRooms" and displayName="GetRooms", the incoming and outgoing messages will look as follows.
For the incoming message:
<GetRooms>
<string>{data}
</string>
</GetRooms>
For the outgoing message:
<GetRoomsResponse>
<GetRoomsResult>
<string>{data}</string>
</GetRoomsResult>
</GetRoomsResponse>
Now let's get to the code:
In Visual Studio, open the HotelAdapterOutboundHandler.cs file.
Add the using System.Xml and using System.IO directives.
Find the Execute method, and replace it with the following code:
/// <summary>
/// Executes the request message on the target system
/// and returns a response message.
/// If there isn't a response, this method should return null
/// </summary>
public Message Execute(Message message, TimeSpan timeout)
{
OperationMetadata om = this.MetadataLookup.
GetOperationDefinitionFromInputMessageAction(
message.Headers.Action, tim-
eout);
if (om == null)
{
throw new AdapterException("Invalid action " +
message.Headers.Action);
}
//actions are specified in the proxy files
//or can be configured in WCF-custom adapter
switch (message.Headers.Action)
{
case "Hotel/GetRooms":
XmlDictionaryReader inputReader =
message.GetReaderAtBodyContents();
// move to the content
while (inputReader.Read())
{
if ((String.IsNullOrEmpty(inputReader.Prefix) &&
inputReader.Name.Equals("hotelName"))
|| inputReader.Name.Equals(
inputReader.Prefix + ":" + "hotelName"))
break;
}
inputReader.Read();
//assume there are 10 rooms available.
string responseValue = inputReader.Value + ":10 rooms";
StringBuilder outputString = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
// Create response message
XmlWriter replywriter =
XmlWriter.Create(outputString, settings);
replywriter.WriteStartElement("GetRoomsResponse",
HotelAdapter.SERVICENAMESPACE);
replywriter.WriteElementString("GetRoomsResponseResult",
responseValue);
replywriter.WriteEndElement();
replywriter.Close();
XmlReader replyReader =
XmlReader.Create(
new StringReader(outputString.ToString()));
// Output Message Format
// <GetRoomsResponse><GetRoomsResult>{rooms}
//</GetRoomsResult></GetRoomsResponse>
// create output message
return Message.CreateMessage(
MessageVersion.Default,
"Hotel/GetRooms/response",
replyReader);
default: throw new AdapterException("Invalid action " +
message.Headers.Action);
}
}
#endregion IOutboundHandler Members
}
5.2. Implementing the IInboundHandler Interface
To enable the inbound capabilities of the adapter, you have to implement the IInboundHandler interface from the Microsoft.ServiceModel.Channels.CommonIInboundHandler interface is located in the Microsoft.ServiceMode.Channels.dll assembly and namespace. The is defined as follows:
public interface IInboundHandler : IConnectionHandler, IDisposable
{
void StartListener(string[] actions, TimeSpan timeout);
void StopListener(TimeSpan timeout);
bool TryReceive(TimeSpan timeout, out Message message,
out IInboundReply reply);
bool WaitForMessage(TimeSpan timeout);
}
Table 16 describes what each method does.
Table 16. IInboundHandler Public Methods
Method | Description |
---|
StartListener | Starts
listening to messages with the provided WS-Addressing actions. If none
is specified, it listens to all or the default actions. |
StopListener | Stops listening. |
TryReceive | Tries to receive an inbound message from the target system. |
WaitForMessage | Waits for the inbound WCF-message from the LOB system. |
Here is the process you have to follow:
In Visual Studio, open the HotelAdapter.cs file.
Add new ServiceName property, as shown here:
public string ServiceNamespace
{
get
{
return SERVICENAMESPACE;
}
}
In Visual Studio, open the HotelInboundHandler.cs file.
Expand the Using Directives region, and make sure it matches the following code snippet:
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.ServiceModel.Channels;
using System.Timers;
using Microsoft.ServiceModel.Channels.Common;
Add the following class member variables to the class:
private Queue<Message> inboundMessages;
private int pollingPeriod;
private Timer pollingTimer;
private Object syncLock;
Replace the HotelInboundHandler constructor with the following code snippet:
public HotelAdapterInboundHandler(HotelAdapterConnection connection
, MetadataLookup metadataLookup)
: base(connection, metadataLookup)
{
pollingPeriod =
connection.ConnectionFactory.Adapter.PollingPeriod;
syncLock = new Object();
}
Add the implementation for the StartListener and StopListener methods, as shown here:
/// <summary>
/// Start the listener
/// </summary>
public void StartListener(string[] actions, TimeSpan timeout)
{
//listen for all actions
//create a Queue to hold inbound messages;
inboundMessages = new Queue<Message>();
//set up a timer to pool for guest arrivals
pollingTimer = new System.Timers.Timer(pollingPeriod * 1000);
pollingTimer.Elapsed +=
new System.Timers.ElapsedEventHandler(CheckArrivals);
}
/// <summary>
/// Stop the listener
/// </summary>
public void StopListener(TimeSpan timeout)
{
if (pollingTimer != null)
{
pollingTimer.Stop();
pollingTimer = null;
}
lock (syncLock)
{
inboundMessages.Clear();
inboundMessages = null;
}
}
Now add the implementation for the TryReceive and WaitForMessage methods:
/// <summary>
/// Tries to receive a message within a specified interval of time.
/// </summary>
public bool TryReceive(TimeSpan timeout,
out System.ServiceModel.Channels.Message message,
out IInboundReply reply)
{
reply = new HotelAdapterInboundReply();
message = null;
//assume timeout is infinite
while (true)
{
lock (syncLock)
{
if (inboundMessages == null)
{
//listener has been closed
return false;
}
if (inboundMessages.Count != 0)
{
message = inboundMessages.Dequeue();
if (message != null)
{
return true;
}
}
}
System.Threading.Thread.Sleep(500);
}
}
/// <summary>
/// Returns a value that indicates whether a message
/// has arrived within a specified interval of time.
/// </summary>
public bool WaitForMessage(TimeSpan timeout)
{
//wait for message to appear in the queue
while (inboundMessages.Count == 0) { };
//check if message is there but don't remove it
Message msg = inboundMessages.Peek();
if (msg != null)
{
return true;
}
return false;
}
Add the implementation for the ElapsedEventHandler callback, as shown in the following code snippet:
private void CheckArrivals(object sender, ElapsedEventArgs args)
{
//poll for example a database
//if new guest found create inbound message
HotelAdapter adapter = this.Connection.ConnectionFactory.Adapter;
String xmlData = String.Format(@"<OnGuestArrived xmlns=""{0}"">
<FirstName>{1}</FirstName>
<LastName>{2}</LastName></OnGuestArrived>",
adapter.ServiceNamespace,
"John",
"Smith");
XmlReader reader = XmlReader.Create(new StringReader(xmlData));
// create WCF message
Message requestMessage =
Message.CreateMessage(MessageVersion.Default
, "Hotel/OnGuestArrived"
, reader);
// add it to inbound queue
inboundMessages.Enqueue(requestMessage);
}
Save and build the project.
You have finished the development stage of the HotelAdapter.
NOTE
In the "scale-out"
architecture, your adapters may be deployed on more than one server. If
this is the case, then in the inbound scenario, there is a potential
danger of sending duplicate messages to the client application when
different instances of your adapter react on the same event on the
target LOB system. Unfortunately, this issue doesn't have a generic
solution, and therefore you, an adapter developer, have to provide the
implementation specific to your target LOB system to prevent duplicate
messages from occurring.