понедельник, 15 июля 2013 г.

Работаем с SAP через .NET connector

Нередко в наших проектах возникают задачи по взаимодействию SharePoint с внешними системами.
Сегодня речь пойдет о интеграции с SAP ERP.

Для интеграции приложений на базе .NET Framework 2.0(3.0/3.5) и 4.0 c SAP (посредством вызова RFC-модулей и веб-сервисов) нам предлагается использовать специальный .NET connector.

SAP предоставляет несколько версий своего коннектора:
  • NCo 3.0 32-bit (x86), .NET 2.0 (also 3.0, and/or 3.5)
  • NCo 3.0 64-bit (x64), .NET 2.0 (also 3.0, and/or 3.5)
  • NCo 3.0 32-bit (x86), .NET 4.0
  • NCo 3.0 64-bit (x64), .NET 4.0
Таким образом, на базе данного коннектора возможно реализовать модуль для интеграции, который может быть развернут как на 32х-битном, так и на 64х-битном web-приложении.
Так как коннектор поставляется для двух версий фреймворка .NET – 2.0 (3.0/3.5) и 4.0, то модуль для интеграции может функционировать в контексте SharePoint 2007/2010/2013 версии.

Пример разработки приложения для получения данных из SAP ERP

Начало работы с коннектором

Для начала необходимо скачать коннектор с сайта SAP Service Marketplace.
Это действие требует авторизации: вы должны быть клиентом SAP с действительным ID клиента и паролем.
Для Visual Studio 2010 нужно загрузить последнюю версию коннектора  в архиве NCo307_Net20.zip
Далее нужно распаковать содержимое архива в папку на вашем компьютере. После распаковки вы обнаружите в папке установочный пакет (Windows Installer Package) с расширением msi. Вам необходимо произвести установку пакета в любую папку на вашем компьютере. Эта папка нам пригодится в дальнейших действиях.

Подготовка проекта в Visual Studio 2010

В рамках данной статьи я продемонстрирую, на примере создания консольного приложения на языке C#, способ получения информации о заказах из SAP.  
Для начала необходимо создать в Visual Studio 2010 новый проект типа "Консольное приложение Windows". Я назвал проект "InvoiceDataConsole".


Настройка подключения к SAP

В новом проекте создаем класс "SAPSystemConnect", реализующий интерфейс "IDestinationConfiguration". Этот класс необходим для управления конфигурацией и подключением к SAP. 
Для использования интерфейса "IDestinationConfiguration" нам потребуется добавить референсы на библиотеки sapnco.dll и sapnco_utils.dll из папки, в которую мы производили установку пакета в предыдущем пункте.

В файле, содержащем описание класса "SAPSystemConnect" добавим namespace "SAP.Middleware.Connector". Используя класс "SAPSystemConnect", добавим реализацию методов интерфейса "IDestinationConfiguration". В листинге ниже показано, как выглядит исходный код класса  "SAPSystemConnect" и реализация методов:
using SAP.Middleware.Connector;

namespace InvoiceDataConsole
{
    class SAPSystemConnect : IDestinationConfiguration
    {
        public bool ChangeEventsSupported()
        {
            return false;
        }
        public event RfcDestinationManager.ConfigurationChangeHandler ConfigurationChanged;
        public RfcConfigParameters GetParameters(string destination)
        {
            //Получаем параметры подключения в порядке: _sysNum,_Host,_User,_Pass,_sysID,_mandt
            string[] pars = destination.Split(',');

            string _sysNum = pars[0];
            string _Host = pars[1];
            string _User = pars[2];
            string _Pass = pars[3];
            string _sysID = pars[4];
            string _mandt = pars[5];

            //Инициализация объекта, содержащего параметры подключения
            RfcConfigParameters _params = new RfcConfigParameters();
            _params.Add(RfcConfigParameters.AppServerHost, _Host);
            _params.Add(RfcConfigParameters.SystemNumber, _sysNum);
            _params.Add(RfcConfigParameters.SystemID, _sysID);
            _params.Add(RfcConfigParameters.Client, _mandt);
            _params.Add(RfcConfigParameters.Language, "RU");
            _params.Add(RfcConfigParameters.User, _User);
            _params.Add(RfcConfigParameters.Password, _Pass);
            _params.Add(RfcConfigParameters.PoolSize, "5");
            _params.Add(RfcConfigParameters.IdleTimeout, "5000");
            _params.Add(RfcConfigParameters.Name, "MYSAPCONNECT");

            return _params;
        }
    }
}
Листинг 1 - Исходный текст файла SAPSystemConnect.cs

Хочу обратить ваше внимание на код метода "GetParameters()": сначала мы получаем набор параметров для подключения и сохраняем их в строковый массив. Далее для удобства присваиваем переменным с "говорящими" названиями соответствующие значения параметров. После этого создаем новый объект "_params" типа "RfcConfigParameters". Этот класс необходим для управления параметрами подключения к SAP.

Параметры подключения к SAP

  • AppServerHost - IP адрес или FQDN сервера, где установлена система SAP
  • SystemNumber - Это номер инстанции, фактически номер сервера приложений, который обрабатывает запрос
  • SystemID - Имя системы в SAP
  • Client - мандант, в котором работает пользователь(например, 800 - работа, 200,140 - разработка)
  • Language - Язык локали, к которой происходит подключение
  • User - Имя пользователя, использующееся для подключения
  • Password - Пароль
  • Poolsize - Размер пула
  • IdleTimeout - Таймаут подключения (в мс)
  • Name - Имя подключения (задается произвольно)

Параметры RFC-модуля

Для взаимодействия с SAP необходимо иметь представление о свойствах вызываемого RFC-модуля, таких как:
  • имя вызываемого RFC-модуля
  • имена входных и выходных параметров
  • имена получаемых структур и таблиц, а также названия их полей
Для удобства я создал отдельный класс с константами "Constants", в котором содержится вся информация для вызова RFC-модуля. Данный класс приведен в листинге ниже.
namespace InvoiceDataConsole
{
    public static class Constants
    {
        public static class InvoiceTable
        {
            //Имя вызываемого модуля            
            public const string Z_RFC_GET_INV = "Z_RFC_GET_INV";

            //Входной параметр для вызова модуля 
            public const string INV_CODE = "INV_CODE01"; 
 
            //Имя основного объекта "счет-фактура"           
            public const string INV_MAIN = "INV_MAIN01"; 
 
            //Выходные параметры объекта "счет-фактура"  
            public const string INV_NAME = "INV_NAME01";
            public const string INV_ADDR = "INV_ADDR01";
            public const string INV_INN = "INV_INN01";
            public const string INV_KPP = "INV_KPP01";

            //Таблица с реквизитами банковских счетов для оплаты            
            public static class INV_BANK_LIST
            {
                //Имя таблицы 
                public const string _FULLTABLENAME = "INV_BANK01";

                //Поля таблицы
                public const string INV_BRNCH = "INV_BRNCH01";
                public const string INV_BVTY = "INV_BVTY01";
                public const string INV_BANK = "INV_BANK01";
            }
        }
    }
}

Листинг 2 - Исходный текст файла Constants.cs

Установка подключения

Для установки подключения к SAP необходимо использовать интерфейс "RFCDestination". Добавим в проект новый класс "Connection", в котором реализуем наш собственный метод "InitConnection()", принимающий в качестве входного параметра строку для подключения к SAP. Данный метод возвращает объект типа "RFCDestination". В листинге ниже видно, что в теле метода "InitConnection()" происходит инициализация нового экземпляра класса "SAPSystemConnect" - sapCfg. Далее этот объект используется для передачи параметров подключения в метод "GetDestination()" класса "RfcDestinationManager", который в свою очередь создает и инициализирует объект типа "RFCDestination".
using SAP.Middleware.Connector;
using System;

namespace InvoiceDataConsole
{
    public class Connection
    {
        public static RfcDestination InitConnection(string connection)
        {
            try
            {
                SAPSystemConnect sapCfg = new SAPSystemConnect();
                RfcDestination rfcDest = RfcDestinationManager.GetDestination(sapCfg.GetParameters(connection));
                return rfcDest;
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: " + ex.Message);
                return null;
            }
        }
    }
}

Листинг 3 - Исходный текст файла Connection.cs

Написание собственного класса для хранения и отображения информации по счету-фактуре

Создадим класс "InvoiceData", в котором будет реализовано получение и вывод информации по счету-фактуре.
В самом классе укажем строковые свойства, в которые будет сохранятся информация только по заданному счету:
  • INV_NAME - название организации
  • INV_ADDR - адрес организации
  • INV_INN - ИНН организации
  • INV_KPP - КПП организации

Далее определим список "BankData", в который будет сохранятся таблица с реквизитами банковских счетов для оплаты - объектов типа "table_INV_BANK".
Данный объект содержит свойства:
  • INV_BRNCH - номер отделения банка
  • INV_BCODE - номенклатурный номер банка
  • INV_BANK - наименование отделения банка

Для запроса данных из SAP напишем реализацию метода "GetDetails()".
Вначале необходимо инициализировать обращение к репозиторию в SAP - rfcDest.Repository.
После этого необходимо создать  и инициализировать объект "rfc" типа "IRfcFunction", используя метод "CreateFunction()", которому нужно передать название RFC-модуля. Используя метод "SetValue()" мы передаем входной параметр (INV_CODE - номер счета) в вызываемый модуль. Сам же вызов осуществляется в методе "Invoke()".

Далее нам необходимо получить основную информацию по счету-фактуре.
Для этого создаем и инициализируем объект "mainStruct" типа "IRfcStructure", используя метод "GetStructure()", которому нужно передать имя основного объекта "счет-фактура" (INV_MAIN). После этого, используя метод "GetValue()" и названия выходных параметров объекта "счет-фактура", заполняем свойства основного класса "InvoiceData".

Чтобы получить информацию по реквизитам банковских счетов для оплаты счета-фактуры, выполним следующие действия.
Создадим и инициализируем объект "invTable" типа "IRfcTable", используя метод "GetTable()", которому нужно передать имя таблицы с реквизитами (_FULLTABLENAME). Далее, перебирая в цикле строки в таблице "invTable", заполняем список "BankData" реквизитами банковских счетов.

Для отображения информации по "счету-фактуре" и банковским реквизитам в консоли, напишем реализацию метода "Display()", который (как я надеюсь) в комментариях не нуждается.
Исходный текст класса "InvoiceData" приведен в листинге ниже.
using SAP.Middleware.Connector;
using System.Collections.Generic;
using NAME = InvoiceDataConsole.Constants.InvoiceTable;
using System;

namespace InvoiceDataConsole
{
    public class InvoiceData
    {        
        public string INV_NAME { get; set; }
        public string INV_ADDR { get; set; }
        public string INV_INN { get; set; }
        public string INV_KPP { get; set; }

        public class table_INV_BANK
        {
            public string INV_BRNCH { get; set; }
            public string INV_BCODE { get; set; }
            public string INV_BANK { get; set; }
        }
        private List <table_INV_BANK> _bankData = new List <table_INV_BANK>();
        public List <table_INV_BANK> BankData
        {
            get { return _bankData; }
            set { _bankData = value; }
        }

        public void GetDetails(RfcDestination rfcDest, string code)
        {
            RfcRepository repo = rfcDest.Repository;            
            IRfcFunction rfc = repo.CreateFunction(NAME.Z_RFC_GET_INV);
            rfc.SetValue(NAME.INV_CODE, code);
            rfc.Invoke(rfcDest);

            IRfcStructure mainStruct = rfc.GetStructure(NAME.INV_MAIN);
            this.INV_ADDR = mainStruct.GetValue(NAME.INV_ADDR).ToString();
            this.INV_INN = mainStruct.GetValue(NAME.INV_INN).ToString();
            this.INV_KPP = mainStruct.GetValue(NAME.INV_KPP).ToString();
            this.INV_NAME = mainStruct.GetValue(NAME.INV_NAME).ToString();

            IRfcTable invTable = rfc.GetTable(NAME.INV_BANK_LIST._FULLTABLENAME);
            if (invTable.RowCount > 0)
            {
                for (int cuIndex = 0; cuIndex < invTable.RowCount; cuIndex++)
                {
                    table_INV_BANK data = new table_INV_BANK();
                    invTable.CurrentIndex = cuIndex;

                    data.INV_BANK = invTable.CurrentRow.GetValue(NAME.INV_BANK_LIST.INV_BANK).ToString();
                    data.INV_BRNCH = invTable.CurrentRow.GetValue(NAME.INV_BANK_LIST.INV_BRNCH).ToString();
                    data.INV_BCODE = invTable.CurrentRow.GetValue(NAME.INV_BANK_LIST.INV_BVTY).ToString();
                    this.BankData.Add(data);
                }
            }
        }

        public void Display()
        {
            Console.WriteLine(); Console.WriteLine();
            Console.WriteLine("Display invoice data");
            Console.WriteLine("INV_NAME: " + this.INV_NAME);
            Console.WriteLine("INV_ADDR: " + this.INV_ADDR);
            Console.WriteLine("INV_INN: " + this.INV_INN);
            Console.WriteLine("INV_KPP: " + this.INV_KPP);            

            Console.WriteLine();
            Console.WriteLine("Display banking details");
            foreach (var item in this.BankData)
            {               
                Console.WriteLine("INV_BANK: " + item.INV_BANK);
                Console.WriteLine("INV_BRNCH: " + item.INV_BRNCH);
                Console.WriteLine("INV_BCODE: " + item.INV_BCODE);
                Console.WriteLine();
            }
        }
    }
}

Листинг 4 - Исходный текст файла InvoiceData.cs

Собираем все вместе

Структура итогового проекта изображена на рисунке ниже.

Нам осталось написать код главной программы "Main".
Вначале задаем строку для подключения к SAP, в которой указываем необходимые значения параметров через запятую.
Далее инициализируем назначение удаленного вызова "rfcDest", используя метод "InitConnection()" из нашего класса. Для проверки доступности подключения к SAP используется метод "Ping()".
После проверки создаем новый экземпляр "invoice" нашего класса "InvoiceData".
С помощью метода "GetDetails()" получаем информацию из SAP по счету-фактуре с номером "1234" и заполняем данными наш объект  "invoice".
Для вывода информации в консоль вызываем метод Display().

Коннектор предоставляет несколько специальных классов-исключений, которые перехватываются с помощью стандартного try{}catch{}.

Листинг файла с главной программой приведен ниже.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SAP.Middleware.Connector;
using SAP.Middleware;

namespace InvoiceDataConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            //Задаем строку подключения с параметрами: sysNum,Host,User,Pass,sysID,mandt
            string ConnectionString = @"10,http://saphost,User,Password,TST,800";

            //Инициализация подключения
            RfcDestination rfcDest = Connection.InitConnection(ConnectionString);
            
            //Открываем скоуп для удаленного вызова
            RfcSessionManager.BeginContext(rfcDest);

            try
            {
                //Проверка подключения к SAP
                Console.Write("Testing connection to SAP: ");
                rfcDest.Ping();

                Console.Write("CONNECTION_SUCCESSFUL!");

                InvoiceData invoice = new InvoiceData();
                invoice.GetDetails(rfcDest, "1234");
                invoice.Display();
            }
            catch (RfcCommunicationException ex)
            {
                //Ошибка подключения
                ShowError(ex);
            }
            catch (RfcLogonException ex)
            {
                //Ошибка аутентификации учетной записи, под которой производится подключение
                ShowError(ex);
            }
            catch (RfcAbapRuntimeException ex)
            {
                //Ошибка выполнения ABAP
                ShowError(ex);
            }
            catch (RfcAbapBaseException ex)
            {
                //Ошибка ABAP 
                ShowError(ex);
            }
            catch (Exception ex)
            {
                //Общая ошибка
                RfcSessionManager.EndContext(rfcDest);
                ShowError(ex);
            }
            finally
            {
                //Закрываем скоуп
                RfcSessionManager.EndContext(rfcDest);
                Console.WriteLine("Press key...");
                Console.ReadKey();
            }
        }

        private static void ShowError(Exception ex)
        {
            Console.WriteLine("Error: " + ex.Message);
        }
    }
}

Листинг 5 - Исходный текст файла Program.cs

Результат работы программы изображен на рисунке ниже.

Подведем итоги

Как вы успели заметить, на второй взгляд, во взаимодействии с SAP через коннектор нет ничего архисложного.
Настройка и инициализация подключения, описание классов для получения и обработки данных из нужных структур в SAP - все это доступно благодаря .NET коннектору!
Надеюсь, что этот пост поможет вам начать работу с коннектором.
Если у вас появятся предложения по улучшению данного материала, то оставьте свой комментарий ниже.


Ссылки по теме 

1. Используемые в статье материалы

2. Дополнительные материалы с портала SAP