This article explains that how we can connect third party applications with SAP using .NET connector (NCO). This is crucial in today’s world as system landscape consist of heterogeneous applications. These third party applications are as crucial as SAP.

Let us take an example of an eCommerce application. Since this is a customer facing platform, you may need prices, taxes, minimum order quantity and other system validations to come from SAP. This ensures that we take order at the correct price for something which is in stock. Another integration business case is a CRM application where you may want to enable sales representatives to submit accurate quote to the end customer.

Integrating applications eliminate manual steps and improve efficiency. Look at below video on how we have integrated order to cash cycle from an eCommerce application named Magento with SAP.

Now lets get to know how we can push data from and to SAP in a real time manner. For this we have taken an example to showcase how we can pull information using remote function call.  However before we go on to that lets understand a few basic things first.

Read more about best practices while integrating SAP with third party applications.


Integrating using SAP NCO – Understanding Technology Stack

SAP .Net connector connects Microsoft .Net application and SAP systems by using the SAP RFC. This is called as NCo. We can connect by using the Java, which is called the JCO. We will go with the .Net connector. Before moving forward these are prerequisites to understanding the process easily. Must have basic knowledge of development using C# and ASP.Net For developing the connector you will require the following Dll’s. There are 32 bit (x86) and 64-bit versions are available.

  • sapnco.dll
  • sapnco_utils.dll

SAP Integration Services by Veon Contact Banner


Where to get NCO 3.0

The SAP .NET Connector 3.0.20 can be downloaded from the SAP Service Marketplace.

  • Navigate to SAP Connector for Microsoft .NET
  • Download SAP Connector for Microsoft .NET Version 3.0

This will require a username and password. If you are a .net developer, probably you do not have it. Ask Your SAP Basis team to download and get it for you because they have the access to SAP marketplace.

Key fields

Given below are the key fields you will need to generate a remote session with SAP. These are available with the SAP Basis team or the system administrators.

  • SAP_USERNAME
  • SAP_APPSERVERHOST
  • SAP_PASSWORD
  • SAP_SYSNUM
  • SAP_CLIENT
  • SAP_LANGUAGE
  • SAP_POOLSIZE

Getting Started – Launch the Visual Studio

So now that we have all the credentials and requirements fulfilled, lets get started with the development of a sample integration program.

  • Create a web application SAP_NCO_ConnectionDemo and add reference of sapnco.dll and sapnco_utils.dll
  • Add a new class called RfcConfig that you will use to read the configuration(created this class in Utils folder)
  • Add SAP.Middleware.Connector namespace to this class which comes from the dll’s that you just added as a reference in your project
using System;
using System.web;
using System.Configuration;
using SAP.Middleware.Connector;

namespace SAP_NCO_ConnectionDemo.Utils
{
	class RfcConfig
	{
	
	}
}

To read the destination, SAP Connector provides an interface called IDestinationConfiguration. This interface provides a method called GetParameters. Inherit IDestinationConfiguration to your class and right click on it to implement an interface. In the below, we have made some changes after the implementation of the interface.

using System;
using SAP.Middleware.Connector;

namespace SAP_NCO_ConnectionDemo.Utils
{
    class RfcConfig : IDestinationConfiguration
    {
        #region IDestinationConfiguration
        public bool ChangeEventsSupported()
        {
            return true;
        }

        RfcDestinationManager.ConfigurationChangeHandler changeHandler;
        public event RfcDestinationManager.ConfigurationChangeHandler ConfigurationChanged
        {
            add { changeHandler += value; }
            remove { changeHandler -= value; }
        }

        public RfcConfigParameters GetParameters(string destinationName)
        {
			
        }
	
	}
}

GetParameters method returns RfcConfigParameters which is a class in SAP Connector DLL. Update the code to read the config values.

 public RfcConfigParameters GetParameters(string destinationName)
        {
            RfcConfigParameters rfcParams = new RfcConfigParameters();

            rfcParams.Add(RfcConfigParameters.AppServerHost, "SAP_HOST");
            rfcParams.Add(RfcConfigParameters.SystemNumber, "SAP_SYSNUM");
            rfcParams.Add(RfcConfigParameters.SystemID, "SAP_SYSID ");
            rfcParams.Add(RfcConfigParameters.User, "SAP_USER");
            rfcParams.Add(RfcConfigParameters.Password, "SAP_PASSWORD");
            rfcParams.Add(RfcConfigParameters.Client, "SAP_CLIENT");
            rfcParams.Add(RfcConfigParameters.Language, "EN");
            rfcParams.Add(RfcConfigParameters.PoolSize, "5");

            return rfcParams;

        }

Now we are able to get the configuration, next step is to test the connection. It can be called in a class where you would connect to the SAP. But the SAP connector has a method called RegisterDestinationConfiguration. All the destinations that have been needed to be registered first before connecting to the SAP.

You can do this step just before connecting to the SAP. For this Create one more class as RfcClient. Register the SAP configuration using the RegisterDestinationConfiguration. If the destination comes as null handle the exceptions.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SAP.Middleware.Connector;
using System.Xml.Linq;
using System.Globalization;

namespace SAP_NCO_ConnectionDemo.Utils
{
    class RfcClient
    {
        const string CONFIG_NAME = "connectiontest";
        RfcDestination _ECCsystem;
        private RfcConfig config;

        public RfcDestination GetDestination(string destinationName)
        {
            try
            {
                _ECCsystem = null;
                _ECCsystem = RfcDestinationManager.GetDestination(destinationName);
            }
            catch
            {
                Console.WriteLine("Environment Not Registered", string.Format("SAP environment not registered for conn : '{0}'", destinationName));
            }

            if (_ECCsystem == null)
            {
                try
                {
                    RfcDestinationManager.RegisterDestinationConfiguration(config);
                    _ECCsystem = RfcDestinationManager.GetDestination(destinationName);
                    Console.WriteLine("SAP Environment Registered", string.Format("SAP environment registered for conn : '{0}'", destinationName));
                }
                catch
                {
                    throw new Exception(string.Format("SAP Environment Registration Process Failed for conn : {0}", destinationName));
                }
            }

            return _ECCsystem;
        }

        public RfcClient()
        {
            config = new RfcConfig();
            _ECCsystem = GetDestination(CONFIG_NAME);
        }

Now define a method to test the connection. In this method, we are sending a request to Standard SAP BAPI request.

public bool testConnection()
        {
            bool state = false;
            string rfcRequest = "<RFC_READ_TABLE><QUERY_TABLE>MARD</QUERY_TABLE><DELIMITER>*</DELIMITER><ROWSKIPS>0</ROWSKIPS><ROWCOUNT>0</ROWCOUNT><TABLE><OPTIONS><ROW><TEXT>MATNR IN (</TEXT></ROW><ROW><TEXT>'testConnection'</TEXT></ROW><ROW><TEXT>)</TEXT></ROW></OPTIONS></TABLE></RFC_READ_TABLE>";

            Utils.RfcClient client = new Utils.RfcClient();
            try
            {
                XElement response = client.PullRequestToSAPrfc(rfcRequest);
                state = true;
            }
            catch (RfcLogonException ex)
            {
                Console.Write("Logon Failed");
            }
            catch (RfcInvalidStateException ex)
            {
                Console.Write("RFC Failed");
            }
            catch (RfcBaseException ex)
            {
                Console.WriteLine("communication error" + ex.Message);
            }
            catch (Exception ex)
            {
                Console.Write("Connection error");
            }
            finally
            {
                //client.disconnectDestination();
            }
            return state;
        }

In the testconnection method, we have called the PullRequestTo SAPrfc function which will create an rfc request packet, sends the request to the SAP and returns the SAP response. This is shown in below.

public XElement PullRequestToSAPrfc(string XMLRequest)
        {
            IRfcFunction requestFn;
            requestFn = PrepareRfcFunctionFromXML(XElement.Parse(XMLRequest));

            RfcSessionManager.BeginContext(_ECCsystem);
            requestFn.Invoke(_ECCsystem);
            RfcSessionManager.EndContext(_ECCsystem);

            XElement XMLResponse = PrepareXMLFromrfc(requestFn);

            return XMLResponse;
        }
requestFn.Invoke(_ECCsystem);  is the line where the .Net application actually calls the SAP Function.
In PullRequestTo SAPrfc, we have used  the two other functions which will creates a rfc request from XML i.e, PrepareRfcFunctionFromXML.
public IRfcFunction PrepareRfcFunctionFromXML(XElement xmlFunction)
        {
            RfcRepository repo = _ECCsystem.Repository;
            IRfcFunction RfcFunction = repo.CreateFunction(xmlFunction.Name.ToString());
            foreach (XElement xelement in xmlFunction.Elements())
            {
                if (xelement.Name.ToString().Equals("TABLE"))
                {
                    if (NotProcessSpecialTable(xelement))
                        continue;
                    IRfcTable options = RfcFunction.GetTable(xelement.Descendants().First().Name.ToString());
                    foreach (XElement row in xelement.Elements().First().Elements())
                    {
                        options.Append();
                        foreach (XElement rowElement in row.Elements())
                        {
                            string elementName = rowElement.Name.ToString();
                            RfcElementMetadata elementMeta = options.GetElementMetadata(elementName);
                            var elementValue = getValueAsMetadata(ref elementMeta, rowElement.Value);
                            if (elementValue is string && string.IsNullOrEmpty((string)elementValue)) { continue; }
                            options.SetValue(elementName, elementValue);
                        }
                    }
                }
                else if (xelement.Name.ToString().Equals("STRUCT"))
                {
                    IRfcStructure options = RfcFunction.GetStructure(xelement.Descendants().First().Name.ToString());
                    foreach (XElement structElement in xelement.Elements().First().Elements())
                    {
                        string elementName = structElement.Name.ToString();
                        RfcElementMetadata elementMeta = options.GetElementMetadata(elementName);
                        var elementValue = getValueAsMetadata(ref elementMeta, structElement.Value);
                        if (elementValue is string && string.IsNullOrEmpty((string)elementValue)) { continue; }
                        options.SetValue(elementName, elementValue);
                    }
                }
                else
                {
                    string elementName = xelement.Name.ToString();
                    RfcElementMetadata elementMeta = RfcFunction.GetElementMetadata(elementName);
                    var elementValue = getValueAsMetadata(ref elementMeta, xelement.Value);
                    if (elementValue is string && string.IsNullOrEmpty((string)elementValue)) { continue; }
                    RfcFunction.SetValue(elementName, elementValue);
                }
            }
            return RfcFunction;
        }

Create an instance of RFCRepository which has all the functions into it.

RfcRepository repo = _ECCsystem.Repository;

Using this repository call the method CreateFunction and provide the RFC name of SAP that you need to call. You need to make sure that the exact function name is available in SAP.

IRfcFunction RfcFunction = repo.CreateFunction(xmlFunction.Name.ToString());

In PrepareXMLrfcfrom method we have converted the Rfc request to XML format.

public XElement PrepareXMLFromrfc(IRfcFunction rfcFunction)
        {
            var XMLRoot = new XElement(rfcFunction.Metadata.Name);
            for (int functionIndex = 0; functionIndex < rfcFunction.ElementCount; functionIndex++)
            {
                var functionMatadata = rfcFunction.GetElementMetadata(functionIndex);
                if (functionMatadata.DataType == RfcDataType.TABLE)
                {
                    var rfcTable = rfcFunction.GetTable(functionMatadata.Name);
                    var XMLTable = new XElement(functionMatadata.Name);
                    foreach (IRfcStructure rfcStracture in rfcTable)
                    {
                        XElement XMLRow = new XElement("ROW");
                        for (int i = 0; i < rfcStracture.ElementCount; i++)
                        {
                            RfcElementMetadata rfcElementMetadata = rfcStracture.GetElementMetadata(i);
                            if (rfcElementMetadata.DataType == RfcDataType.BCD)
                            { XMLRow.Add(new XElement(rfcElementMetadata.Name, rfcStracture.GetString(rfcElementMetadata.Name))); }
                            else
                            {
                                XMLRow.Add(new XElement(rfcElementMetadata.Name, rfcStracture.GetString(rfcElementMetadata.Name)));
                            }
                        }

                        XMLTable.Add(XMLRow);
                    }
                    XMLRoot.Add(XMLTable);
                }
                else if (functionMatadata.DataType == RfcDataType.STRUCTURE)
                {
                    var rfcStructure = rfcFunction.GetStructure(functionMatadata.Name);
                    XElement XMLRow = new XElement(functionMatadata.Name);
                    for (int elementIndex = 0; elementIndex < rfcStructure.ElementCount; elementIndex++)
                    {
                        RfcElementMetadata eleMeta = rfcStructure.GetElementMetadata(elementIndex);
                        XMLRow.Add(new XElement(eleMeta.Name, rfcStructure.GetString(eleMeta.Name)));
                    }
                    XMLRoot.Add(XMLRow);
                }
                else
                {
                    RfcElementMetadata rfcElement = rfcFunction.GetElementMetadata(functionIndex);
                    XMLRoot.Add(new XElement(rfcElement.Name, rfcFunction.GetString(rfcElement.Name)));
                }
            }
            return XMLRoot;
        }

Below function is used for the data types.

private object getValueAsMetadata(ref RfcElementMetadata elementMeta, string value)
        {
            switch (elementMeta.DataType)
            {
                case RfcDataType.BCD:
                    return value;
                case RfcDataType.NUM:
                    if (value.Contains("."))
                    {
                        int elementValue;
                        int.TryParse(value, out elementValue);
                        return elementValue;
                    }
                    else
                    {
                        return Convert.ToInt32(value);
                    }
                case RfcDataType.INT1:
                    return Convert.ToInt32(value);
                case RfcDataType.INT2:
                    return Convert.ToInt32(value);
                case RfcDataType.INT4:
                    return Convert.ToInt32(value);
                case RfcDataType.INT8:
                    return Convert.ToInt64(value);
                case RfcDataType.CHAR:
                    return value;
                case RfcDataType.DATE:
                    return DateTime.ParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture);


                default:
                    return string.Empty;
            }
        }

The next step is to create a class which will act as a middleman between the .Net application and the SAP. This class will be responsible for connecting to SAP Server using SAP Connector, sending data and fetching data. Add a new class called SAPConnection. Add using SAP.Middleware.Connector namespace, as this is also going to use the SAP Connector DLL and call the testconnection Method

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using SAP.Middleware.Connector;

namespace SAP_NCO_ConnectionDemo
{
    class SAPConnection
    {

        static void Main(string
[] args) { Utils.RfcClient ref1=new Utils.RfcClient(); ref1.testConnection(); } } }

If the connection test is failed, It will provide the respected Exception which is defined in testconnection Method like credential mismatches. If the connection test is a success, You will get the response from the SAP.

Now proceed to test with some actual data. First I will show this on SAP side. Here we are calling the standard SAP bapi RFC_READ_TABLE for retrieving the product sales area.

Test function Module initial screen

Result:

Display DATA

Prepare the same request in XML string format and Call the PullRequestToSAPrfc method as shown.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using SAP.Middleware.Connector;

namespace SAP_NCO_ConnectionDemo
{
    class SAPConnection
    {

        static void Main(string[] args)
        {
            Utils.RfcClient ref1=new Utils.RfcClient();
            ref1.testConnection();
            Console.Write("hello");
            XElement result=ref1.PullRequestToSAPrfc("<RFC_READ_TABLE><QUERY_TABLE>MVKE</QUERY_TABLE><DELIMITER>*</DELIMITER><ROWSKIPS>0</ROWSKIPS><ROWCOUNT>0</ROWCOUNT><TABLE><OPTIONS><ROW><TEXT>MATNR EQ 'R-1000'</TEXT></ROW></OPTIONS></TABLE><TABLE><FIELDS><ROW><FIELDNAME>VKORG</FIELDNAME><OFFSET>000000</OFFSET><LENGTH>000000</LENGTH></ROW><ROW><FIELDNAME>VTWEG</FIELDNAME><OFFSET>000000</OFFSET><LENGTH>000000</LENGTH></ROW></FIELDS></TABLE></RFC_READ_TABLE>");
            Console.WriteLine(result);
            Console.ReadKey();
        }
    }
}

Now we have successfully connected to the SAP. Below is the SAP response printed on the console.

Successfully connected to the SAP

In this way, we can connect to the SAP by using the SAP connector 3.0.


Looking for a partner to integration SAP?

We can help deliver SAP integration projects. Reach out to us for a non-obligation consultation.

Contact us for a free assessment