Receive Inbound Operations from the SAP System Using the WCF Channel Model

To act as an RFC server and receive operations invoked by the SAP system (such as sending an IDOC or invoking an RFC), you must create a channel listener that can listen for messages from a SAP Program ID over a System.ServiceModel.Channels.IReplyChannel channel shape.

A channel listener (System.ServiceModel.Channels.IChannelListener) is a WCF communication object that can be used to receive messages from specific WCF endpoints. The channel listener functions as a factory from which you can create channels over which messages invoked by a client (the SAP system) can be received by your service. You create a channel listener by from a Microsoft.Adapters.SAP.SAPBinding object by invoking the BuildChannelListener method. You supply an SAP connection URI that specifies the SAP Program ID from which inbound operations will be received to this method.

The SAP adapter supports the IReplyChannel channel shape. IReplyChannel channels support an inbound request-response message exchange pattern. That is, a pattern in which an external program sends a request message over the channel and your program returns a response.

For an overview of how to receive operations using an IReplyChannel in WCF, see Service Channel-Level Programming.

This section covers the following topics that are specific to receiving operations from a SAP system:

  • How to filter for specific operations using the channel listener.

  • How to raise an exception on the SAP system.

  • Streaming inbound flat-file IDOCs from the SAP adapter.

  • How to receive operations from the SAP system.

How Do I Filter Operations Using the Channel Listener?

Using an InboundActionCollection to Filter Operations

The WCF LOB Adapter SDK provides the Microsoft.ServiceModel.Channels.InboundActionCollection class to enable you to filter operations that are received by a channel listener and passed to your application code. To filter for specific operations, you create an instance of this class by using the listener endpoint URI. Then you add the (request) message action for each target operation to the collection. Finally, you add the inbound action collection to a System.ServiceModel.Channels.BindingParameterCollection object and then pass this binding parameter collection into the call to create the channel listener.

If the SAP system invokes an operation that is not in the inbound action collection:

  • The SAP adapter returns an EXCEPTION exception to the caller on the SAP system with the following message: "The incoming RFC call [RFC_NAME] on the Rfc Server is not handled". In this message, [RFC_NAME] is the name of the RFC (for example, IDOC_INBOUND_ASYNCHRONOUS).

  • The adapter throws a Microsoft.ServiceModel.Channels.Common.AdapterException with a message that indicates the operation that was received. For an example of how to use this exception, see the example at the end of this topic.

    The following code example shows how to use an InboundActionCollection to create a channel listener that filters for a single RFC, Z_RFC_MKD_DIV.

// The connection Uri must specify listener parameters (or an R-type destination in saprfc.ini)  
// and credentials.  
Uri listeneraddress =  
    new Uri("sap://User=YourUserName;Passwd=YourPassword;Client=800;Lang=EN;@a/YourSAPHost/00?ListenerGwServ=SAPGATEWAY&ListenerGwHost=YourSAPHost&ListenerProgramId=SAPAdapter");  
  
// Create a binding and set AcceptCredentialsInUri to true  
SAPBinding binding = new SAPBinding();  
binding.AcceptCredentialsInUri = true;  
  
// Create an InboundActionCollection and add the message actions to listen for,  
// only the actions added to the InboundActionCollection are received on the channel.  
// In this case a single action is specified: http://Microsoft.LobServices.Sap/2007/03/Rfc/Z_RFC_MKD_DIV  
InboundActionCollection actions = new InboundActionCollection(listeneraddress);  
actions.Add("http://Microsoft.LobServices.Sap/2007/03/Rfc/Z_RFC_MKD_DIV");  
  
// Create a BindingParameterCollection and add the InboundActionCollection  
BindingParameterCollection bpcol = new BindingParameterCollection();  
bpcol.Add(actions);  
  
// Create the channel listener by specifying the binding parameter collection (to filter for the Z_RFC_MKD_DIV action)  
listener = binding.BuildChannelListener<IReplyChannel>(listeneraddress, bpcol);  

Manually Filtering Operations

If you do not specify an inbound action collection for the channel listener, then all operations invoked by the SAP system will be passed to your code. You can manually filter such operations by checking the message action of inbound requests.

There may also be scenarios in which you want to filter an operation based on its content. For example if you are receiving IDOCs in:

  • String format (the ReceiveIDocFormat binding property is String); all IDOCs are received using the ReceiveIdoc operation.

  • Rfc format (the ReceiveIDocFormat binding property is Rfc); all IDOCs are received using either the IDOC_INBOUND_ASYNCHRONOUS RFC or the INBOUND_IDOC_PROCESS RFC.

    In this scenario you may want to implement filtering based on specific IDOC parameters (such as the IDOC type) in your code.

    When you filter operations manually, you can return a fault to the SAP adapter for operations that you don't handle. This will raise the EXCEPTION exception to the caller on the SAP System. You can also return an empty response if you don't want to raise an exception on SAP.

    The following code shows how to filter manually for the Z_RFC_MKD_DIV operation.

// Get the message from the channel  
RequestContext rc = channel.ReceiveRequest();  
Message reqMessage = rc.RequestMessage;  
  
// Filter based on the message action.  
if (reqMessage.Headers.Action == "http://Microsoft.LobServices.Sap/2007/03/Rfc/Z_RFC_MKD_DIV")  
{  
  
    // Process message and return response.  
    ...  
  
}  
else  
{  
    // If this isn't the correct message return an empty response or a fault message.  
    // This example returns an empty response.  
    rc.Reply(Message.CreateMessage(MessageVersion.Default, reqMessage.Headers.Action + "/response"));  
}  

How Do I Raise an Exception on the SAP System?

To indicate an error to the caller on the SAP system you can reply to a request message with a SOAP fault. When you return a SOAP fault to the SAP adapter, the adapter returns an EXCEPTION exception to the caller on the SAP system. The exception message is created from the elements of the SOAP fault.

The SAP adapter creates the message for the SAP EXCEPTION according to the following order of precedence:

  1. If the SOAP fault contains a detail object, the adapter serializes the detail to a string and the exception message is set to this string.

  2. If the SOAP fault contains a reason, the exception message is set to its value.

  3. Otherwise, the adapter serializes the MessageFault object itself to a string and the exception message is set to this string.

Note

The adapter only uses the fault message to create the exception message returned in the exception raised on the SAP system; therefore, the values that you set for these entities is completely up to you.

WCF provides the System.ServiceModel.Channels.MessageFault class to encapsulate an in-memory representation of a SOAP fault. You can use any of the static, overloaded MessageFault.CreateFault methods to create a new SOAP fault from which you can then create a fault message by invoking the appropriate Message.CreateMessage overload. WCF also provides overloads of CreateMessage that create a fault message without using a MessageFault object.

You use the System.ServiceModel.Channels.RequestContext.Reply method to return the fault message to the adapter. The SAP adapter ignores the message action for fault messages, so you can set the message action to any value.

The following example shows how to return a fault message to the SAP adapter. This example omits the steps to create the channel listener and channel.

RequestContext rc = channel.ReceiveRequest();  
…  
// Start processing the inbound message  
…  
// If an error is encountered return a fault to the SAP system  
// This example uses CreateMessage overload to create a fault message.  
// The overload takes a SOAP version, fault code, reason, and message action  
// The SAP adapter ignores the message action for a fault so you can set it to any value you want.   
Message faultMessage = Message.CreateMessage(MessageVersion.Default, new FaultCode("SAP Example Fault"), "Testing SAP Faults", rc.RequestMessage.Headers.Action + "/fault");  
  
rc.Reply(faultMessage);  

Streaming Inbound Flat-File IDOCs from the SAP Adapter

You receive flat-file (string) IDOCs from the adapter in the inbound ReceiveIdoc operation. The IDOC data is represented as a string under a single node in this operation. For this reason, the SAP adapter supports node-value streaming on the request message. To perform node-value streaming, you must consume the request message for the ReceiveIdoc operation by invoking the Message.WriteBodyContents method with a System.Xml.XmlDictionaryWriter that is capable of streaming the IDOC data. For information about how to do this, see Streaming Flat-File IDOCs in SAP using the WCF Channel Model.

How Do I Receive Operations from a SAP System Using an IReplyChannel?

To receive operations from a SAP system by using the WCF channel model, perform the following steps.

To receive operations from the SAP system using an IReplyChannel

  1. Create an instance of SAPBinding and set the binding properties required to for the operations you want to receive. At a minimum you must set the AcceptCredentialsInUri binding property to true. To act as a tRFC server, you must set the TidDatabaseConnectionString binding property. For more information about binding properties, see Read about BizTalk Adapter for mySAP Business Suite Binding Properties.

    SAPBinding binding = new SAPBinding();  
    binding.AcceptCredentialsInUri = true;  
    
  2. Create a BindingParameterCollection and add an InboundActionCollection that contains the actions of the operations that you want to receive. The adapter will return an exception to the SAP system for all other operations. This step is optional. For more information, see Receiving Inbound Operations from the SAP System Using the WCF Channel Model.

    InboundActionCollection actions = new InboundActionCollection(listeneraddress);  
    actions.Add("http://Microsoft.LobServices.Sap/2007/03/Rfc/Z_RFC_MKD_DIV");  
    BindingParameterCollection bpcol = new BindingParameterCollection();  
    bpcol.Add(actions);  
    
  3. Create a channel listener by invoking BuildChannelListener<IReplyChannel> method on the SAPBinding and open it. You specify the SAP connection URI as one of the parameters to this method. The connection URI must contain parameters for an RFC Destination on the SAP system. For more information about the SAP connection URI, see Create the SAP system connection URI. If you created a BindingParameterCollection in step 3, you also specify this when you create the channel listener.

    Uri listeneraddress =  
        new Uri("sap://User=YourUserName;Passwd=YourPassword;Client=800;Lang=EN;@a/YourSAPHost/00?ListenerGwServ=SAPGATEWAY&ListenerGwHost=YourSAPHost&ListenerProgramId=SAPAdapter");  
    IChannelListener<IReplyChannel> listener = binding.BuildChannelListener<IReplyChannel>(connectionUri, bpcol);  
    listener.Open();  
    
  4. Get an IReplyChannel channel by invoking the AcceptChannel method on the listener and open it.

    IReplyChannel channel = listener.AcceptChannel();  
    channel.Open();  
    
  5. Invoke ReceiveRequest on the channel to get the request message for the next operation from the adapter.

    RequestContext rc = channel.ReceiveRequest();  
    
  6. Consume the request message sent by the adapter. You get the request message from the RequestMessage property of the RequestContext. You can consume the message using either an XmlReader or an XmlDictionaryWriter.

    XmlReader reader = (XmlReader)rc.RequestMessage.GetReaderAtBodyContents();  
    
  7. Complete the operation by returning a response or fault to the SAP system:

    1. Process the message and return a response to the SAP system by returning the response message to the adapter. This example returns an empty message.

      respMessage = Message.CreateMessage(MessageVersion.Default, rc.RequestMessage.Headers.Action + "/response");  
      rc.Reply(respMessage);  
      
    2. Return an exception to the SAP system by returning a fault message to the adapter. You can use any value for the message action, fault code, and reason.

      MessageFault fault = MessageFault.CreateFault(new FaultCode("ProcFault"), "Processing Error");  
      Message respMessage = Message.CreateMessage(MessageVersion.Default, fault, String.Empty);  
      rc.Reply(respMessage);  
      
  8. Close the request context after you have sent the message.

    rc.Close();  
    
  9. Close the channel when you have completed processing the request.

    channel.Close()  
    

    Important

    You must close the channel after you have finished processing the operation. Failure to close the channel may affect the behavior of your code.

  10. Close the listener when you are finished receiving operations from the SAP system.

    listener.Close()  
    

    Important

    You must explicitly close the listener when you are done using it; otherwise, your program may not behave properly. Closing the listener does not close channels created using the listener. You must also explicitly close each channel created using the listener.

Example

The following example receives an RFC, Z_RFC_MKD_DIV from the SAP system. This RFC divides two numbers. The implementation in this example uses an InboundActionCollection to filter for the Z_RFC_MKD_DIV operation and does the following when a message is received:

  • If the divisor is non-zero, it writes the result of the division to the console and returns it to the SAP system.

  • If the divisor is zero, it writes the resulting exception message to the console and returns a fault to the SAP system.

  • If any other operation is sent by the SAP system, it writes a message to the console. In this case, the adapter itself returns a fault to the SAP system.

using System;  
using System.Collections.Generic;  
using System.Text;  
  
using System.Runtime.Serialization;  
using System.Xml;  
using System.IO;  
  
// Add WCF, Adapter LOB SDK, and SAP Adapter namepaces  
using System.ServiceModel;  
using Microsoft.Adapters.SAP;  
using Microsoft.ServiceModel.Channels;  
  
// Add this namespace to use Channel Model   
using System.ServiceModel.Channels;  
  
// Include this namespace for Adapter LOB SDK and SAP exceptions  
using Microsoft.ServiceModel.Channels.Common;  
  
// This sample demonstrates using the adapter as an rfc server over a channel.  
// The sample implements an RFC, Z_RFC_MKD_DIV that divides two numbers and returns the result  
// 1)  A SAPBinding instance is created and configured (AcceptCredentialsInUri is set true)  
// 2)  A binding parameter collection is created with an InboundAction collection that specifies  
//     target RFC (Z_RFC_MKD_DIV) so that only messages with this action will be received by the  
//     listener (and channel).  
// 3)  An <IReplyChannel> listener is created from the binding and binding parameter collection  
// 4)  A channel is created and opened to receive a request  
// 6)  When Z_RFC_MKD_DIV is received the two parameters are divided and the parameters and result  
//     are written to the console, then the result is returned to the adapter by using a template  
//     message.  
// 7)  If a divide by 0 occurs the exception message is written to the console and a  
//     fault is returned to the SAP system  
// 8)  If any other operation is received an error message is written to the console and the adapter  
///    returns a fault to the SAP system  
// 9)  IMPORTANT you must close the channel and listener to deregister them from the SAP Program ID.  
namespace SapRfcServerCM  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            // Variables to hold the listener and channel  
            IChannelListener<IReplyChannel> listener = null;  
            IReplyChannel channel = null;  
  
            Console.WriteLine("Sample started");  
            Console.WriteLine("Initializing and creating channel listener -- please wait");  
            try  
            {  
  
                // The connection Uri must specify listener parameters (or an R-type destination in saprfc.ini)  
                // and also credentials.  
                Uri listeneraddress =  
                    new Uri("sap://User=YourUserName;Passwd=YourPassword;Client=800;Lang=EN;@a/YourSAPHost/00?ListenerGwServ=SAPGATEWAY&ListenerGwHost=YourSAPHost&ListenerProgramId=SAPAdapter");  
  
                // Create a binding -- set AcceptCredentialsInUri true  
                SAPBinding binding = new SAPBinding();  
                binding.AcceptCredentialsInUri = true;  
  
                // Create a binding parameter collection with a list of SOAP actions to listen on  
                // in this case: http://Microsoft.LobServices.Sap/2007/03/Rfc/Z_RFC_MKD_DIV  
                // This ensures that only these actions are received on the channel.  
                InboundActionCollection actions = new InboundActionCollection(listeneraddress);  
                actions.Add("http://Microsoft.LobServices.Sap/2007/03/Rfc/Z_RFC_MKD_DIV");  
                BindingParameterCollection bpcol = new BindingParameterCollection();  
                bpcol.Add(actions);  
  
                // Pass the Uri and the binding parameter collection (to specify the Z_RFC_MKD_DIV action)  
                listener = binding.BuildChannelListener<IReplyChannel>(listeneraddress, bpcol);  
  
                Console.WriteLine("Opening listener");  
                // Open the listener  
                listener.Open();  
  
                // Get an IReplyChannel  
                channel = listener.AcceptChannel();  
  
                Console.WriteLine("Opening channel");  
                // Open the channel  
                channel.Open();  
  
                Console.WriteLine("\nReady to receive Z_RFC_MKD_DIV RFC");  
  
                try  
                {  
                    // Get the message from the channel  
                    RequestContext rc = channel.ReceiveRequest();  
  
                    // Get the request message sent by SAP  
                    Message reqMessage = rc.RequestMessage;  
  
                    // get the message body  
                    XmlReader reader = reqMessage.GetReaderAtBodyContents();  
  
                    reader.ReadStartElement("Z_RFC_MKD_DIV");  
                    reader.ReadElementString("DEST");  
                    int x_in = int.Parse(reader.ReadElementString("X"));  
                    int y_in = int.Parse(reader.ReadElementString("Y"));  
                    reader.ReadEndElement();  
  
                    Console.WriteLine("\nRfc Received");  
                    Console.WriteLine("X =\t\t" + x_in);  
                    Console.WriteLine("Y =\t\t" + y_in);  
  
                    Message messageOut = null;  
  
                    try   
                    {  
                        int result_out = x_in/y_in;  
  
                        Console.WriteLine("RESULT =\t" + result_out.ToString());  
  
                        string out_xml = "<Z_RFC_MKD_DIVResponse xmlns=\"http://Microsoft.LobServices.Sap/2007/03/Rfc/\"><RESULT>" + result_out + "</RESULT></Z_RFC_MKD_DIVResponse>";  
                        StringReader sr = new StringReader(out_xml);  
                        reader = XmlReader.Create(sr);  
  
                        // create a response message  
                        // be sure to specify the response action  
                        messageOut = Message.CreateMessage(MessageVersion.Default, reqMessage.Headers.Action + "/response", reader);  
  
                    }  
                    catch (DivideByZeroException ex)  
                    {  
                        Console.WriteLine();  
                        Console.WriteLine(ex.Message + " Returning fault to SAP");  
  
                        // Create a message that contains a fault  
                        // The fault code and message action can be any value  
  
                        messageOut = Message.CreateMessage(MessageVersion.Default, new FaultCode("Fault"), ex.Message, string.Empty);  
                    }  
  
                    // Send the reply  
                    rc.Reply(messageOut);  
  
                    // Close the request context  
                    rc.Close();  
  
                }  
                catch (AdapterException aex)  
                {  
                    // Will get here if the message received was not in the InboundActionCollection  
                    Console.WriteLine();  
                    Console.WriteLine(aex.Message);  
                }  
  
                // Wait for a key to exit  
                Console.WriteLine("\nHit <RETURN> to end");  
                Console.ReadLine();  
            }  
            catch (ConnectionException cex)  
            {  
                Console.WriteLine("Exception occurred connecting to the SAP system");  
                Console.WriteLine(cex.InnerException.Message);  
            }  
            catch (TargetSystemException tex)  
            {  
                Console.WriteLine("Exception occurred on the SAP system");  
                Console.WriteLine(tex.InnerException.Message);  
            }  
            catch (Exception ex)  
            {  
                Console.WriteLine("Exception is: " + ex.Message);  
                if (ex.InnerException != null)  
                {  
                    Console.WriteLine("Inner Exception is: " + ex.InnerException.Message);  
                }  
            }  
            finally  
            {  
                // IMPORTANT: close the channel and listener to stop listening on the Program ID  
                if (channel != null)  
                {  
                    if (channel.State == CommunicationState.Opened)  
                        channel.Close();  
                    else  
                        channel.Abort();  
                }  
  
                if (listener != null)  
                {  
                    if (listener.State == CommunicationState.Opened)  
                        listener.Close();  
                    else  
                        listener.Abort();  
                }  
            }  
        }  
    }  
}  

See Also

Develop applications using the WCF Channel Model