Recepción de operaciones de entrada desde el sistema SAP mediante el modelo de canal WCF
Para actuar como un servidor RFC y recibir operaciones invocadas por el sistema SAP (por ejemplo, enviar un IDOC o invocar una RFC), debe crear un agente de escucha de canal que pueda escuchar mensajes de un identificador de programa sap a través de una forma de canal System.ServiceModel.Channels.IReplyChannel .
Un agente de escucha de canal (System.ServiceModel.Channels.IChannelListener) es un objeto de comunicación WCF que se puede usar para recibir mensajes de puntos de conexión WCF específicos. El agente de escucha del canal funciona como un generador desde el que puede crear canales a través de los cuales el servicio puede recibir los mensajes invocados por un cliente (el sistema SAP). Para crear un agente de escucha de canal a partir de un objeto Microsoft.Adapters.SAP.SAPBinding , invoque el método BuildChannelListener . Se proporciona un URI de conexión de SAP que especifica el identificador del programa de SAP desde el que se recibirán las operaciones entrantes a este método.
El adaptador de SAP admite la forma del canal IReplyChannel . Los canales IReplyChannel admiten un patrón de intercambio de mensajes de solicitud-respuesta entrante. Es decir, un patrón en el que un programa externo envía un mensaje de solicitud a través del canal y el programa devuelve una respuesta.
Para obtener información general sobre cómo recibir operaciones mediante un IReplyChannel en WCF, consulte Programación de service Channel-Level.
En esta sección se tratan los temas siguientes específicos para recibir operaciones de un sistema SAP:
Filtrado de operaciones específicas mediante el agente de escucha del canal.
Cómo generar una excepción en el sistema SAP.
Streaming de IDOC de archivos planos de entrada desde el adaptador de SAP.
Cómo recibir operaciones del sistema SAP.
¿Cómo se filtran las operaciones mediante el agente de escucha del canal?
Uso de inboundActionCollection para filtrar operaciones
El SDK del adaptador de LOB de WCF proporciona la clase Microsoft.ServiceModel.Channels.InboundActionCollection para permitirle filtrar las operaciones recibidas por un agente de escucha del canal y pasarlas al código de la aplicación. Para filtrar operaciones específicas, cree una instancia de esta clase mediante el URI del punto de conexión del agente de escucha. A continuación, agregue la acción de mensaje (solicitud) para cada operación de destino a la colección. Por último, agregue la colección de acciones de entrada a un objeto System.ServiceModel.Channels.BindingParameterCollection y, a continuación, pase esta colección de parámetros de enlace a la llamada para crear el agente de escucha del canal.
Si el sistema SAP invoca una operación que no está en la colección de acciones de entrada:
El adaptador de SAP devuelve una excepción EXCEPTION al autor de la llamada en el sistema SAP con el siguiente mensaje: "No se controla la llamada RFC entrante [RFC_NAME] en el servidor Rfc". En este mensaje, [RFC_NAME] es el nombre de la RFC (por ejemplo, IDOC_INBOUND_ASYNCHRONOUS).
El adaptador produce una excepción Microsoft.ServiceModel.Channels.Common.AdapterException con un mensaje que indica la operación que se recibió. Para obtener un ejemplo de cómo usar esta excepción, vea el ejemplo al final de este tema.
En el ejemplo de código siguiente se muestra cómo usar inboundActionCollection para crear un agente de escucha de canal que filtre por una única 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);
Operaciones de filtrado manual
Si no especifica una colección de acciones de entrada para el agente de escucha del canal, todas las operaciones invocadas por el sistema SAP se pasarán al código. Puede filtrar manualmente estas operaciones comprobando la acción de mensaje de las solicitudes entrantes.
También puede haber escenarios en los que quiera filtrar una operación en función de su contenido. Por ejemplo, si está recibiendo IDOC en:
Formato de cadena (la propiedad de enlace ReceiveIDocFormat es String); todos los IDOC se reciben mediante la operación ReceiveIdoc.
Formato rfc (la propiedad de enlace ReceiveIDocFormat es Rfc); todos los IDOC se reciben mediante el RFC de IDOC_INBOUND_ASYNCHRONOUS o el RFC de INBOUND_IDOC_PROCESS.
En este escenario, es posible que desee implementar el filtrado en función de parámetros IDOC específicos (como el tipo IDOC) en el código.
Al filtrar manualmente las operaciones, puede devolver un error al adaptador de SAP para las operaciones que no controla. Esto generará la excepción EXCEPTION al autor de la llamada en el sistema SAP. También puede devolver una respuesta vacía si no desea generar una excepción en SAP.
En el código siguiente se muestra cómo filtrar manualmente para la operación de 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"));
}
¿Cómo se genera una excepción en el sistema SAP?
Para indicar un error al autor de la llamada en el sistema SAP, puede responder a un mensaje de solicitud con un error soap. Cuando se devuelve un error soap al adaptador de SAP, el adaptador devuelve una excepción EXCEPTION al autor de la llamada en el sistema SAP. El mensaje de excepción se crea a partir de los elementos del error soap.
El adaptador de SAP crea el mensaje para la excepción de SAP según el siguiente orden de prioridad:
Si el error soap contiene un objeto de detalle, el adaptador serializa el detalle en una cadena y el mensaje de excepción se establece en esta cadena.
Si el error soap contiene un motivo, el mensaje de excepción se establece en su valor.
De lo contrario, el adaptador serializa el propio objeto MessageFault en una cadena y el mensaje de excepción se establece en esta cadena.
Nota
El adaptador solo usa el mensaje de error para crear el mensaje de excepción devuelto en la excepción generada en el sistema SAP; por lo tanto, los valores que estableció para estas entidades son totalmente los que se han creado.
WCF proporciona la clase System.ServiceModel.Channels.MessageFault para encapsular una representación en memoria de un error soap. Puede usar cualquiera de los métodos estáticos sobrecargados MessageFault.CreateFault para crear un nuevo error soap desde el que, a continuación, puede crear un mensaje de error invocando la sobrecarga Message.CreateMessage adecuada. WCF también proporciona sobrecargas de CreateMessage que crean un mensaje de error sin usar un objeto MessageFault .
Use el método System.ServiceModel.Channels.RequestContext.Reply para devolver el mensaje de error al adaptador. El adaptador de SAP omite la acción de mensaje de los mensajes de error, por lo que puede establecer la acción de mensaje en cualquier valor.
En el ejemplo siguiente se muestra cómo devolver un mensaje de error al adaptador de SAP. En este ejemplo se omiten los pasos para crear el agente de escucha y el canal del canal.
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 IDOC desde el adaptador de SAP
Recibirá IDOC de archivo plano (cadena) del adaptador en la operación ReceiveIdoc de entrada. Los datos del IDOC se representan como una cadena en un único nodo de esta operación. Por este motivo, el adaptador de SAP admite el streaming de valor de nodo en el mensaje de solicitud. Para realizar streaming de nodo-valor, debe consumir el mensaje de solicitud para la operación ReceiveIdoc invocando el método Message.WriteBodyContents con un System.Xml. XmlDictionaryWriter que es capaz de transmitir los datos del IDOC. Para obtener información sobre cómo hacerlo, vea Streaming Flat-File IDOC en SAP mediante el modelo de canal WCF.
¿Cómo recibo operaciones de un sistema SAP mediante un IReplyChannel?
Para recibir operaciones de un sistema SAP mediante el modelo de canal WCF, realice los pasos siguientes.
Para recibir operaciones del sistema SAP mediante un IReplyChannel
Cree una instancia de SAPBinding y establezca las propiedades de enlace necesarias para las operaciones que desea recibir. Como mínimo, debe establecer la propiedad de enlace AcceptCredentialsInUri en true. Para actuar como servidor tRFC, debe establecer la propiedad de enlace TidDatabaseConnectionString . Para obtener más información sobre las propiedades de enlace, vea Leer sobre el adaptador de BizTalk para mySAP Business Suite Binding Properties.
SAPBinding binding = new SAPBinding(); binding.AcceptCredentialsInUri = true;
Cree un BindingParameterCollection y agregue una clase InboundActionCollection que contenga las acciones de las operaciones que desea recibir. El adaptador devolverá una excepción al sistema SAP para todas las demás operaciones. Este paso es opcional. Para obtener más información, consulte Recepción de operaciones de entrada desde el sistema SAP mediante el modelo de canal WCF.
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);
Cree un agente de escucha de canal invocando el método IReplyChannel> BuildChannelListener< en SAPBinding y ábralo. Especifique el URI de conexión de SAP como uno de los parámetros de este método. El URI de conexión debe contener parámetros para un destino RFC en el sistema SAP. Para más información sobre el URI de conexión de SAP, consulte Creación del URI de conexión del sistema SAP. Si creó un BindingParameterCollection en el paso 3, también lo especificará al crear el agente de escucha del canal.
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();
Obtenga un canal IReplyChannel invocando el método AcceptChannel en el agente de escucha y ábralo.
IReplyChannel channel = listener.AcceptChannel(); channel.Open();
Invoque ReceiveRequest en el canal para obtener el mensaje de solicitud para la siguiente operación del adaptador.
RequestContext rc = channel.ReceiveRequest();
Consuma el mensaje de solicitud enviado por el adaptador. Obtiene el mensaje de solicitud de la propiedad RequestMessage de RequestContext. Puede consumir el mensaje mediante XmlReader o XmlDictionaryWriter.
XmlReader reader = (XmlReader)rc.RequestMessage.GetReaderAtBodyContents();
Complete la operación devolviendo una respuesta o un error al sistema SAP:
Procese el mensaje y devuelva una respuesta al sistema SAP devolviendo el mensaje de respuesta al adaptador. En este ejemplo se devuelve un mensaje vacío.
respMessage = Message.CreateMessage(MessageVersion.Default, rc.RequestMessage.Headers.Action + "/response"); rc.Reply(respMessage);
Devuelva una excepción al sistema SAP devolviendo un mensaje de error al adaptador. Puede usar cualquier valor para la acción del mensaje, el código de error y el motivo.
MessageFault fault = MessageFault.CreateFault(new FaultCode("ProcFault"), "Processing Error"); Message respMessage = Message.CreateMessage(MessageVersion.Default, fault, String.Empty); rc.Reply(respMessage);
Cierre el contexto de la solicitud después de haber enviado el mensaje.
rc.Close();
Cierre el canal cuando haya completado el procesamiento de la solicitud.
channel.Close()
Importante
Debe cerrar el canal una vez que haya terminado de procesar la operación. Si no se cierra el canal, puede afectar al comportamiento del código.
Cierre el agente de escucha cuando haya terminado de recibir operaciones del sistema SAP.
listener.Close()
Importante
Debe cerrar explícitamente el agente de escucha cuando haya terminado de usarlo; De lo contrario, es posible que el programa no se comporte correctamente. Cerrar el agente de escucha no cierra los canales creados mediante el agente de escucha. También debe cerrar explícitamente cada canal creado mediante el agente de escucha.
Ejemplo
En el ejemplo siguiente se recibe un RFC, Z_RFC_MKD_DIV del sistema SAP. Esta RFC divide dos números. La implementación de este ejemplo usa inboundActionCollection para filtrar por la operación de Z_RFC_MKD_DIV y hace lo siguiente cuando se recibe un mensaje:
Si el divisor no es cero, escribe el resultado de la división en la consola y lo devuelve al sistema SAP.
Si el divisor es cero, escribe el mensaje de excepción resultante en la consola y devuelve un error al sistema SAP.
Si el sistema SAP envía cualquier otra operación, escribe un mensaje en la consola. En este caso, el propio adaptador devuelve un error al sistema 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();
}
}
}
}
}