若要充当 RFC 服务器并接收 SAP 系统调用的作(例如发送 IDOC 或调用 RFC),必须创建一个通道侦听器,该侦听器可以通过 System.ServiceModel.Channels.IReplyChannel 通道形状侦听来自 SAP 程序 ID 的消息。
通道侦听器(System.ServiceModel.Channels.IChannelListener)是一个 WCF 通信对象,可用于接收来自特定 WCF 终结点的消息。 通道侦听器充当工厂,可以在其中创建客户端(SAP 系统)调用的消息的通道,服务可以接收这些消息。 通过调用 BuildChannelListener 方法,从 Microsoft.Adapters.SAP.SAPBinding 对象创建通道侦听器。 提供一个 SAP 连接 URI,用于指定 SAP 计划 ID,从而接收入站操作。
SAP 适配器支持 IReplyChannel 通道形状。 IReplyChannel 通道支持入站请求响应消息交换模式。 也就是说,外部程序通过通道发送请求消息并返回响应的模式。
有关如何在 WCF 中使用 IReplyChannel 接收操作的概述,请参阅 服务 Channel-Level 编程。
本部分涵盖以下特定于从 SAP 系统接收操作的主题:
如何使用通道侦听器筛选特定操作。
如何在 SAP 系统上引发异常。
从 SAP 适配器流式传入平面文件 IDOC。
如何从 SAP 系统接收操作。
如何使用通道侦听器筛选操作?
使用 InboundActionCollection 筛选操作
WCF LOB 适配器 SDK 提供 Microsoft.ServiceModel.Channels.InboundActionCollection 类,以便于您筛选由通道侦听器接收并传递到应用程序代码的操作。 若要筛选特定操作,请使用侦听器终结点 URI 来创建这个类的实例。 然后将每个目标操作的(请求)消息操作添加到集合中。 最后,将入站作集合添加到 System.ServiceModel.Channels.BindingParameterCollection 对象,然后将此绑定参数集合传递到调用中创建通道侦听器。
如果 SAP 系统调用的操作不在入站动作集合中:
SAP 适配器向 SAP 系统上的调用方返回异常,并显示以下消息:“未处理 Rfc 服务器上的传入 RFC 调用 [RFC_NAME]。 在此消息中,[RFC_NAME] 是 RFC 的名称(例如,IDOC_INBOUND_ASYNCHRONOUS)。
适配器会抛出 Microsoft.ServiceModel.Channels.Common.AdapterException ,并显示指示收到的操作的消息。 有关如何使用此异常的示例,请参阅本主题末尾的示例。
下面的代码示例演示如何使用 InboundActionCollection 创建用于筛选单个 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);
手动筛选操作
如果未为通道监听器指定入站操作集合,SAP 系统调用的所有操作都将被传递给您的代码。 可以通过检查入站请求的消息操作来手动筛选此类操作。
你可能还希望根据其内容对操作进行筛选的情况。 例如,如果要在以下项中接收 IDOC:
字符串格式( ReceiveIDocFormat 绑定属性为 String);所有 IDOC 都使用 ReceiveIdoc作接收。
Rfc 格式( ReceiveIDocFormat 绑定属性为 Rfc):所有 IDOC 都使用 IDOC_INBOUND_ASYNCHRONOUS RFC 或 INBOUND_IDOC_PROCESS RFC 接收。
在此方案中,你可能希望根据代码中的特定 IDOC 参数(如 IDOC 类型)实现筛选。
手动筛选操作时,对于未处理的操作,可以将错误返回到 SAP 适配器。 这会向 SAP 系统上的调用方引发 EXCEPTION 异常。 如果不想在 SAP 上引发异常,还可以返回空响应。
以下代码演示如何手动筛选Z_RFC_MKD_DIV操作。
// 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"));
}
如何在 SAP 系统上引发异常?
若要向 SAP 系统上的调用方指示错误,可以使用 SOAP 错误回复请求消息。 当您将一个 SOAP 错误返回到 SAP 适配器时,适配器会在 SAP 系统上向调用者返回一个 EXCEPTION 异常。 异常消息是从 SOAP 错误的元素创建的。
SAP 适配器根据以下优先级顺序为 SAP EXCEPTION 创建消息:
如果 SOAP 错误包含详细信息对象,适配器会将详细信息序列化为字符串,并将异常消息设置为此字符串。
如果 SOAP 错误包含原因,异常消息将设置为其值。
否则,适配器会将 MessageFault 对象本身序列化为字符串,异常消息设置为此字符串。
注释
适配器仅使用错误消息创建在 SAP 系统上引发的异常中返回的异常消息;因此,为这些实体设置的值完全由你决定。
WCF 提供 System.ServiceModel.Channels.MessageFault 类来封装 SOAP 错误的内存中表示形式。 可以使用任何静态重载 MessageFault.CreateFault 方法创建新的 SOAP 错误,然后通过调用相应的 Message.CreateMessage 重载来创建错误消息。 WCF 还提供 CreateMessage 的重载,这些重载在不使用 MessageFault 对象的情况下创建错误消息。
使用 System.ServiceModel.Channels.RequestContext.Reply 方法将错误消息返回到适配器。 SAP 适配器会忽略错误消息的消息动作,因此可以将其设置为任何值。
以下示例演示如何将错误消息返回到 SAP 适配器。 此示例省略了创建通道侦听器和通道的步骤。
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);
从 SAP 适配器流式传输入站 Flat-File IDOC
在入站 ReceiveIdoc 操作中,从适配器接收平面文件(字符串)IDOCs。 IDOC 数据表示为在此操作中单个节点下的字符串。 因此,SAP 适配器支持请求消息上的节点值流式处理。 要执行节点值流式处理,必须先使用可流式传输 IDOC 数据的 System.Xml.XmlDictionaryWriter,然后调用 Message.WriteBodyContents 方法以处理 ReceiveIdoc 操作的请求消息。 有关如何执行此操作的信息,请参阅 使用 WCF 通道模型在 SAP 中流式传输 Flat-File IDOC 的指南。
如何使用 IReplyChannel 从 SAP 系统接收操作?
若要使用 WCF 通道模型从 SAP 系统接收操作,请执行以下步骤。
使用 IReplyChannel 从 SAP 系统接收操作
创建 SAPBinding 的实例,并设置操作所需的绑定属性以便接收。 至少必须将 AcceptCredentialsInUri 绑定属性设置为 true。 若要充当 tRFC 服务器,必须设置 TidDatabaseConnectionString 绑定属性。 有关绑定属性的详细信息,请参阅 有关 mySAP Business Suite 绑定属性的 BizTalk 适配器的信息。
SAPBinding binding = new SAPBinding(); binding.AcceptCredentialsInUri = true;
创建 BindingParameterCollection 并添加一个 InboundActionCollection,其中包含要接收的操作。 对于所有其他操作,适配器将返回异常给 SAP 系统。 此步骤是可选的。 有关详细信息,请参阅 使用 WCF 通道模型从 SAP 系统接收入站操作。
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);
通过在 SAPBinding 上调用 BuildChannelListener<IReplyChannel> 方法创建通道侦听器,并打开创建的侦听器。 将 SAP 连接 URI 指定为此方法的参数之一。 连接 URI 必须包含 SAP 系统上 RFC 目标的参数。 有关 SAP 连接 URI 的详细信息,请参阅 创建 SAP 系统连接 URI。 如果在步骤 3 中创建 BindingParameterCollection ,则创建通道侦听器时也会指定此项。
谨慎
此示例或指南引用敏感信息,例如连接字符串或用户名和密码。 切勿在代码中硬编码这些值,并确保使用最安全的身份验证来保护机密数据。 有关详细信息,请参阅以下文档:
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();
通过在侦听器上调用 AcceptChannel 方法并打开它来获取 IReplyChannel 通道。
IReplyChannel channel = listener.AcceptChannel(); channel.Open();
在通道上调用 ReceiveRequest ,从适配器获取下一作的请求消息。
RequestContext rc = channel.ReceiveRequest();
处理由适配器发送的请求消息。 从 RequestContext 的 RequestMessage 属性获取请求消息。 可以使用 XmlReader 或 XmlDictionaryWriter 来处理消息。
XmlReader reader = (XmlReader)rc.RequestMessage.GetReaderAtBodyContents();
通过向 SAP 系统返回响应或错误来完成操作。
处理消息后,通过将响应消息返回给适配器来向 SAP 系统返回响应。 此示例返回一条空消息。
respMessage = Message.CreateMessage(MessageVersion.Default, rc.RequestMessage.Headers.Action + "/response"); rc.Reply(respMessage);
通过将故障消息返回到适配器,将异常返回至 SAP 系统。 可以将任何值用于消息操作、错误代码和原因。
MessageFault fault = MessageFault.CreateFault(new FaultCode("ProcFault"), "Processing Error"); Message respMessage = Message.CreateMessage(MessageVersion.Default, fault, String.Empty); rc.Reply(respMessage);
发送消息后关闭请求上下文。
rc.Close();
完成处理请求后关闭通道。
channel.Close()
重要
操作完成后,您必须关闭通道。 未能关闭通道可能会影响代码的行为。
完成从 SAP 系统接收操作后关闭侦听器。
listener.Close()
重要
使用完侦听器后,必须显式关闭侦听器;否则,程序可能无法正常运行。 关闭侦听器不会关闭使用侦听器创建的通道。 还必须显式地关闭每个使用侦听器创建的通道。
示例:
以下示例从 SAP 系统接收 RFC,Z_RFC_MKD_DIV。 此 RFC 进行两个数字的除法。 此示例中的实现使用 InboundActionCollection 筛选 Z_RFC_MKD_DIV 操作,并在收到消息时执行以下操作:
如果除数为非零,则会将除法的结果写入控制台,并将其返回到 SAP 系统。
如果除数为零,它将生成的异常消息写入控制台,并将错误返回到 SAP 系统。
如果 SAP 系统发送了任何其他操作,它会将消息写入控制台。 在这种情况下,适配器本身会向 SAP 系统返回错误。
谨慎
此示例或指南引用敏感信息,例如连接字符串或用户名和密码。 切勿在代码中硬编码这些值,并确保使用最安全的身份验证来保护机密数据。 有关详细信息,请参阅以下文档:
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();
}
}
}
}
}