Step 7: Implement the Synchronous Outbound Handler for the Echo Adapter

Step 7 of 9

Time to complete: 30 minutes

In this step, you implement the synchronous outbound capability of the Echo adapter. According to the WCF LOB Adapter SDK, to support the synchronous outbound capability, you must implement the Microsoft.ServiceModel.Channels.Common.IOutboundHandler interface. For the Echo adapter, the Adapter Development Wizard automatically generates one derived class called EchoAdapterOutboundHandler.

In the following sections, you update the EchoAdapterOutboundHandler class to get a better understanding of how to implement the Microsoft.ServiceModel.Channels.Common.IOutboundHandler.Execute%2A, how to parse the incoming WCF request message, and how to generate outgoing WCF response messages.

Prerequisites

Before you begin this step, you must have successfully completed Step 6: Implement the Metadata Resolve Handler for the Echo Adapter. A basic familiarity with Microsoft.ServiceModel.Channels.Common.IOutboundHandler is also helpful.

The IOutboundHandler Interface

The Microsoft.ServiceModel.Channels.Common.IOutboundHandler is defined as:

public interface IOutboundHandler : IConnectionHandler, IDisposable  
{  
    Message Execute(Message message, TimeSpan timeout);  
}  

The Microsoft.ServiceModel.Channels.Common.IOutboundHandler.Execute%2A method executes the incoming WCF request message by invoking the corresponding method on the target system and then returns an outgoing WCF response message. The definitions of its parameters are listed in the following table:

Parameter Definition
message Incoming WCF request message.
timeout Time interval within which this operation should be completed. The operation should throw a System.TimeoutException if the specified timeout is exceeded before the operation is completed.

If your adapter is performing a one-way send (there is no response message expected by your adapter), this method should return null. If your adapter is performing a two-way operation with Microsoft.ServiceModel.Channels.Common.OperationResult equal to Microsoft.ServiceModel.Channels.Common.OperationResult.Empty%2A, this method returns a WCF response message with an empty body. Otherwise, it should return the WCF response message with a body that contains the values in the Microsoft.ServiceModel.Channels.Common.OperationResult object. To construct the response action string, use Microsoft.ServiceModel.Channels.Common.OperationMetadata.OutputMessageAction%2A.

Echo Adapter Synchronous Outbound

Depending on your target system's operations, there are many ways to implement the Microsoft.ServiceModel.Channels.Common.IOutboundHandler.Execute%2A method. For the Echo adapter, there are three outbound operations, and their assigned node IDs and display names are:

  • string[] EchoStrings(string data), node ID = Echo/EchoString, display name=EchoString

  • Greeting[] EchoGreetings(Greeting greeting), node ID=Echo/EchoGreetings, display name=EchoGreetings

  • CustomGreeting EchoCustomGreetingFromFile(Uri greetingInstancePath), nodeID=Echo/EchoCustomGreetingFromFile, display name=EchoGreetings

    To correctly parse the incoming WCF request message and generate the outgoing WCF response message, you must be familiar with the following elements within the SOAP message used by the WCF LOB Adapter SDK:

    For the incoming WCF request message:

  • The WCF input message action = operation's node ID

  • Incoming message body = The start element of the body is <displayname><parameter name>{data}</parameter name></displayname>

    For the outgoing WCF response message:

  • The WCF output message action = operation's node ID + "/response"

  • Outgoing message body = The start element of the body is <displayname + "Response">, followed by <displayname + "Result">, and followed by the <datatype>data</datatype></displayname+"Result></displayname + "Response">

    For example, operation string[] EchoStrings(string data), node ID = Echo/EchoStrings, and display name= EchoStrings:

    The WCF input message action = "Echo/EchoStrings"; and the input body looks as follows, since the parameter name is data.

<EchoStrings>  
<data>{data}  
</data>  
</EchoStrings>  

The WCF output message action = "Echo/EchoStrings/response"; and the output body looks as follows, since the data type is string.

<EchoStringsResponse>  
<EchoStringsResult>  
<string>{data}</string>  
</EchoStringsResult>  
</EchoStringsResponse>  

When parsing the incoming WCF request message, you can use the System.Xml.XmlDictionaryReader to retrieve content within the WCF message; when composing the WCF response message, you can use the System.Xml.XmlWriter to do so.

Implementing the IOutboundHandler

You implement the Execute method of the Microsoft.ServiceModel.Channels.Common.IOutboundHandler. First, gets a Microsoft.ServiceModel.Channels.Common.OperationMetadata object based on the input message action. Then, parses the incoming WCF message and executes the associated echo functionality based on each operation. Finally, creates an outgoing WCF response message by using the format of outgoing message body.

To implement the Execute method of the EchoAdapterOutboundHandler class

  1. In Solution Explorer, double-click the EchoAdapterOutboundHandler.cs file.

  2. In the Visual Studio editor, add the following two using directives to the existing set of using directives.

    using System.Xml;  
    using System.IO;  
    
  3. Inside the Execute method, replace the existing logic with the following:

    1. This logic verifies the requested operation.

    2. It gets the Microsoft.ServiceModel.Channels.Common.OperationMetadata object based on the SOAP input message action.

    3. Based on the action type, it parses the WCF request message and invokes the appropriate operation.

    // Trace input message  
    EchoAdapterUtilities.Trace.Trace(System.Diagnostics.TraceEventType.Verbose, "http://Microsoft.Adapters.Samples.Sql/TraceCode/InputWcfMessage", "Input WCF Message", this, new MessageTraceRecord(message));  
    // Timeout is not supported in this sample  
    OperationMetadata om = this.MetadataLookup.GetOperationDefinitionFromInputMessageAction(message.Headers.Action, timeout);  
    if (om == null)  
    {  
        throw new AdapterException("Invalid operation metadata for " + message.Headers.Action);  
    }  
    if (timeout.Equals(TimeSpan.Zero))  
    {  
        throw new AdapterException("time out is zero");  
    }  
    
    switch (message.Headers.Action)  
    {  
        case "Echo/EchoStrings":  
            return ExecuteEchoStrings(om as ParameterizedOperationMetadata, message, timeout);  
    
        case "Echo/EchoGreetings":  
            return ExecuteEchoGreetings(om as ParameterizedOperationMetadata, message, timeout);  
    
        case "Echo/EchoCustomGreetingFromFile":  
            return ExecuteEchoCustomGreetingFromFile(om, message, timeout);  
    }  
    return null;              
    
  4. Now add the ExecuteEchoStrings method to handle the string[] EchoStrings(string data) operation. This helper function reads the WCF request message, checks to see if the echoInUpperCase URI element is set to true; if so, it converts the input string to upper case as many times as the count variable indicates. Then, it generates the WCF response message in the format of: <EchoStringsResponse><EchoStringResult><string>{data}</string></EchoStringResult></EchoStringsResponse>.

    private Message ExecuteEchoStrings(ParameterizedOperationMetadata om, Message message, TimeSpan timeout)  
    {  
        // ** Read the WCF request  
        // ** <EchoStrings><name>{text}</name></EchoStrings>  
        XmlDictionaryReader inputReader = message.GetReaderAtBodyContents();  
        while (inputReader.Read())  
        {  
            if ((String.IsNullOrEmpty(inputReader.Prefix) && inputReader.Name.Equals("data"))  
                || inputReader.Name.Equals(inputReader.Prefix + ":" + "data")) break;  
        }  
        inputReader.Read();  
        // if the connection property "echoInUpperCase" is set to true, it echoes the data in upper case  
        bool echoInUpperCase = this.Connection.ConnectionFactory.ConnectionUri.EchoInUpperCase;  
        string inputValue = echoInUpperCase ? inputReader.Value.ToUpper() : inputReader.Value;  
        int arrayCount = this.Connection.ConnectionFactory.Adapter.Count;  
    
        // ** Generate the WCF response  
        // ** <EchoStringsResponse><EchoStringResult>{Name}</EchoStringResult></EchoStringsResponse >  
        StringBuilder outputString = new StringBuilder();  
        XmlWriterSettings settings = new XmlWriterSettings();  
        settings.OmitXmlDeclaration = true;  
        XmlWriter replywriter = XmlWriter.Create(outputString, settings);  
        replywriter.WriteStartElement(om.DisplayName + "Response", EchoAdapter.SERVICENAMESPACE);  
        replywriter.WriteStartElement(om.DisplayName + "Result", EchoAdapter.SERVICENAMESPACE);  
        if (om.OperationResult.IsArray)  
        {  
            for (int count = 0; count < arrayCount; count++)  
            {  
                replywriter.WriteElementString("string", "http://schemas.microsoft.com/2003/10/Serialization/Arrays", inputValue);  
            }  
        }  
        replywriter.WriteEndElement();  
        replywriter.WriteEndElement();  
        replywriter.Close();  
        XmlReader replyReader = XmlReader.Create(new StringReader(outputString.ToString()));  
        return Message.CreateMessage(message.Version, om.OutputMessageAction, replyReader);  
    }  
    
  5. Continue by adding the ExecuteEchoGreetings method to handle the EchoGreetings operation. This helper function reads the WCF request message, resolves operation and type by the ResolveOperationMetadata and ResolveTypeMetadata methods of the Microsoft.ServiceModel.Channels.Common.IMetadataResolverHandler interface, and then generates the WCF response message using the format of: <EchoGreetingsResponse><EchoGreetingsResult>…message…</EchoGreetingsResult></EchoGreetingsResponse>.

    private Message ExecuteEchoGreetings(ParameterizedOperationMetadata om, Message message, TimeSpan timeout)  
    {  
        // NOTE this method doesn't return response in upper case based on   
        // connection property echoInUpperCase  
    
        // ** Read the WCF request  
        String inputValue = String.Empty;  
        using (XmlDictionaryReader inputReader = message.GetReaderAtBodyContents())  
        {  
    
            bool foundGreeting = inputReader.ReadToDescendant("greeting");  
            if (foundGreeting)  
            {  
                inputValue = inputReader.ReadInnerXml();  
            }  
        }  
    
        int arrayCount = this.Connection.ConnectionFactory.Adapter.Count;  
    
        // ** Generate the WCF response  
        StringBuilder outputString = new StringBuilder();  
        XmlWriterSettings settings = new XmlWriterSettings();  
        settings.OmitXmlDeclaration = true;  
        XmlWriter replywriter = XmlWriter.Create(outputString, settings);  
        replywriter.WriteStartElement(om.DisplayName + "Response", EchoAdapter.SERVICENAMESPACE);  
        replywriter.WriteStartElement(om.DisplayName + "Result", EchoAdapter.SERVICENAMESPACE);  
        for(int i = 0; i < arrayCount; i++ )  
        {  
            ComplexQualifiedType cqtResult = om.OperationResult.QualifiedType as ComplexQualifiedType;  
            StructuredTypeMetadata tmResult = MetadataLookup.GetTypeDefinition(cqtResult.TypeId, timeout) as StructuredTypeMetadata;  
            replywriter.WriteStartElement(tmResult.TypeName, tmResult.TypeNamespace);  
            replywriter.WriteRaw(inputValue);  
            replywriter.WriteEndElement();  
        }  
        replywriter.WriteEndElement();  
        replywriter.WriteEndElement();  
        replywriter.Close();  
        XmlReader replyReader = XmlReader.Create(new StringReader(outputString.ToString()));  
        return Message.CreateMessage(message.Version, om.OutputMessageAction, replyReader);  
    }  
    
  6. Now add the ExecuteEchoCustomGreetingFromFile method to handle the EchoCustomGreetingFromFile operation. This helper function reads the WCF request message, reads the message from the specified file, and then generates the WCF response message using the format of: <EchoGreetingsFromFileResponse><EchoGreetingsFromFileResult>…message…</EchoGreetingsFromFileResult></EchoGreetingsFromFileResponse>.

    private Message ExecuteEchoCustomGreetingFromFile(OperationMetadata om, Message message, TimeSpan timeout)  
    {  
        // NOTE this method doesn't return response in upper case based on   
        // connection property echoInUpperCase  
    
        // ** Read the WCF request  
        Uri path;  
        using (XmlDictionaryReader inputReader = message.GetReaderAtBodyContents())  
        {  
            inputReader.MoveToContent();  
            inputReader.ReadStartElement(om.DisplayName);  
            inputReader.MoveToContent();  
            // The path contains the location of the XML file that contains the instance of Greeting object to read   
            path = new Uri(inputReader.ReadElementContentAsString());  
            inputReader.ReadEndElement();  
        }  
    
        // ** Generate the WCF response  
        StringBuilder outputString = new StringBuilder();  
        XmlWriterSettings settings = new XmlWriterSettings();  
        settings.OmitXmlDeclaration = true;  
        XmlWriter replywriter = XmlWriter.Create(outputString, settings);  
        replywriter.WriteStartElement(om.DisplayName + "Response", EchoAdapter.SERVICENAMESPACE);  
        replywriter.WriteStartElement(om.DisplayName + "Result", EchoAdapter.SERVICENAMESPACE);  
        // Read the XML file and set it to the reply writer here  
        FileStream stream = new FileStream(path.AbsolutePath, FileMode.Open);  
        XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());  
        xdr.MoveToContent();  
        string rawGreeting = xdr.ReadInnerXml();  
        replywriter.WriteRaw(rawGreeting);  
        replywriter.WriteEndElement();  
        replywriter.WriteEndElement();  
        replywriter.Close();  
        XmlReader replyReader = XmlReader.Create(new StringReader(outputString.ToString()));  
        return Message.CreateMessage(message.Version, om.OutputMessageAction, replyReader);  
    }  
    
  7. In Visual Studio, on the File menu, click Save All.

  8. On the Build menu, click Build Solution. It should compile without errors. If not, ensure that you have followed every step above. Now, you can safely close Visual Studio or continue on to Step 8: Implement the Synchronous Inbound Handler for the Echo Adapter.

What Did I Just Do?

In this step, you learned how to implement the synchronous outbound messaging functionality of the Echo adapter. To do so, you implemented the Microsoft.ServiceModel.Channels.Common.IOutboundHandler.Execute%2A method of the Microsoft.ServiceModel.Channels.Common.IOutboundHandler. This method parsed the incoming WCF request message, performed the necessary actions, and then generated the outgoing WCF response message.

Specifically, for the incoming WCF request message, you used WCF System.ServiceModel.Channels.Message.Headers.Action%2A to retrieve the input message action by further understanding the basic structure of the incoming message body. For the outgoing WCF response message, you used Microsoft.ServiceModel.Channels.Common.OperationMetadata.OutputMessageAction%2A to retrieve the output message action by further understanding the basic structure of the outgoing message body. When parsing and composing WCF messages, you used System.Xml.XmlDictionaryReader to read the incoming WCF request message and used System.Xml.XmlWriter to write an outgoing WCF response message.

Next Steps

Build and deploy the Echo adapter.

See Also

Step 6: Implement the Metadata Resolve Handler for the Echo Adapter
Step 8: Implement the Synchronous Inbound Handler for the Echo Adapter