вторник, 10 сентября 2013 г.

Работа с SharePoint 2010 Client Side Object Model через Javascript

Про SharePoint 2010 Client Side Object Model (CSOM)  уже написано и съедено немало.
Я лишь постараюсь каснуться вопроса применения Javascript для работы с CSOM на ASPX-странице, расположенной в библиотеке на портале SharePoint (Site page).

Создание новой aspx-страницы в SharePoint Designer 2010

Для начала нам потребуется создать пустую aspx-страницу без мастерпейджа. Для этого открываем SharePoint Designer 2010, выбираем нужную библиотеку, в которой наша страница будет расположена (например "Страницы сайта), и через ленту добавляем новую страницу, как показано на рисунке ниже. Отмечу, что страница уже будет содержать минимальную html-разметку.

Дополнительная разметка

В начало страницы нам также нужно добавить секции Import и Register:

<%@ Import Namespace="Microsoft.SharePoint" %> <%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>      
<%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>      

Минимальный набор js-файлов для работы с CSOM

В заголовок страницы нужно добавить самый главный файл - "core.js":
 
<SharePoint:ScriptLink ID="ScriptLink1" Language="javascript" Name="core.js" OnDemand="true" runat="server"/>

Далее в заголовок добавляем ссылки на остальные js-файлы для корректной работы CSOM:
 

<script type="text/javascript" src="/_layouts/MicrosoftAjax.js"></script>
<script type="text/javascript" src="/_layouts/sp.core.debug.js"></script>
<script type="text/javascript" src="/_layouts/sp.runtime.debug.js"></script>
<script type="text/javascript" src="/_layouts/sp.debug.js"></script>


Напишем простой метод, в котором реализуем отображение логина текущего пользователя.
 
    <script type="text/javascript">
        var currentUser;
        function getWebProperties() {
            var userContext1 = new SP.ClientContext.get_current();
            currentUser = userContext1.get_web().get_currentUser();
            userContext1.load(currentUser);
            userContext1.executeQueryAsync(Function.createDelegate(this, this.onSuccess),
            Function.createDelegate(this, this.onFail));
        }
        function onSuccess(sender, args) {
            alert('current user: ' + currentUser.get_loginName().toLowerCase());
        }
        function onFail(sender, args) {
            alert('failed to current user. Error: ' + args.get_message());
        }
    </script>

В итоге исходный код страницы принимает вид:

Результат

После сохранения страницы открываем её в веб-браузере и видим пустую страницу с кнопкой "Get user".
Нажимаем на кнопку и видим сообщение, отображающее логин текущего пользователя!

Пример страницы можно загрузить отсюда.

Полезные ссылки


понедельник, 2 сентября 2013 г.

Подключение LCD дисплея к Raspberry Pi

Кто бы мог подумать еще в 2010 году, что такое явление как "Raspberry Pi" обретет тысячи поклонников по всему миру.
Не смог пройти мимо и я.
Сегодня я покажу на примере как подключать и использовать LCD (хотя аббревиатура уже включает в себя слово "дисплей" далее я будут все равно его использовать) совместно с Raspberry Pi. Скажу сразу: статья ориентирована на тех, кто не первый раз сталкивается с Raspberry.


Пример подключения LCD дисплея к Raspberry Pi

На борту Raspberry Pi имеет особый разъем типа GPIO. К нему-то мы и подключим дисплей.
Распиновка разъема GPIO представлена в таблице ниже:
Таблица 1.

Выбор LCD дисплея

Для наших целей подойдет любой жидкокристаллический знакосинтезирующий (символьный) дисплей на базе микроконтроллера Hitachi HD44780U или его аналогов.
Почему знакосинтезирующий? 
Все просто: в одном разряде можно отобразить только 1 знак из заданного набора, который хранится во внутренней ROM микроконтроллера. У каждого экземпляра LCD может быть свой набор с дополнительными знаками. Более того, микроконтроллеры некоторых LCD дисплеев позволяют создавать и сохранять свои знаки в EEPROM микроконтроллера.
LCD дисплеи бывают 8x2, 16x2, 4x20 и т.д. - строк на количество знаков. Их выпускает куча разных фирм - Winstar, МЭЛТ и другие.
Для сборки прототипа я приобрел дисплей Winstar WH0802A-YYH-CT. 
Документация (datasheet) на дисплей тут.

Сборка прототипа

Для сборки прототипа я раздобыл макетную печатную плату, цветной шлейф, разъем IDC 26P типа "мама" и латунные стойки для монтажа плат.

Разъем IDC я "подписал" для удобства сборки. Файл с подписями можно скачать тут.


Теперь нужно определиться с порядком соединения пинов на разъеме IDC с пинами на GPIO, плюс разобраться как мы подключим питание к нашему дисплею.
Оказывается все просто! В документации находим таблицу с распиновкой порта LCD дисплея для 4-х битного режима и дополняем ее следующим образом:
Таблица 2.
Где GND - это "минус", а +5V - "плюс" питания, которое мы берем все из того же GPIO разъема. 
Подписи GPIO - соответствуют ... ну вы сами догадались)
Данный дисплей имеет подсветку. Для ее включения достаточно подключить LEDA к +5V, а LEDK - к GND.

!ВНИМАНИЕ! 
Для использования LCD дисплея в данной схеме ваш источник питания, который вы подключаете к Raspberry Pi, должен быть рассчитан на потребляемый ток, как минимум, 2А.
!ВНИМАНИЕ! 

Далее паяем, собираем...


Выбор и использование библиотек для работы с LCD дисплеем

Для работы с LCD дисплеем нам нужно написать программу. Делать это я буду на языке Си. Но для компиляции листинга нам потребуется загрузить набор библиотек. Мой выбор пал на  пакет библиотек wiringPi, который был использован в статье. Сам пакет предназначен не только для подключения LCD дисплея.
Процесс установки пакета описан на сайте.

Листинг "mylcd.c" с текстом программы я привожу ниже (по стандарту С99).
 
#include <stdio.h>  //стандартная библиотека ввода-вывода
#include <wiringPi.h>  //библиотека из пакета wiringPi
#include <lcd.h>  //библиотека из пакета wiringPi

int main (void)
{
 printf ("Raspberry Pi LCD test\n") ;
 //Инициализация порта GPIO
 if(wiringPiSetup ()==-1)
 {
  printf ("GPIO Setup failed!\n") ;
 }

 int fd;
 printf ("Start LCD initialization...\n") ;
 //Инициализация LCD
 fd = lcdInit (2,8,4, 11,10, 1,0,2,3,0,0,0,0);

 if(fd==-1)
 {
  printf ("Initialization failed\n") ;
 }
 else
 {
  printf ("GO!\n");

  //Очистка дислпея
  lcdClear(fd);
  
  //Перевод каретки на первую позицию первой строки
  lcdPosition (fd,0,0);
  
  //Вывод форматированного текста
  lcdPrintf(fd, "Hello Pi");

  //Перевод каретки на вторую строку и вывод текста
  lcdPosition (fd,0,1);
  lcdPrintf(fd, " World!");
 }
 return 0;
}
 
В листинге нас особо интересует следующие функции:
  1. wiringPiSetup() - функция для инициализации порта GPIO
  2. lcdInit(int rows, int cols, int bits, int rs, int strb, int d0, int d1, int d2, int d3, int d4, int d5, int d6, int d7)  - функция для инициализации LCD дисплея, где:
    * int rows - число строк дисплея (у нас 2)
    * int cols - число знаков в строке (у нас 8)
    * int rs - маппинг порта wiringPi на управляющий регистр дисплея RS (у нас 11)
    * int strb - маппинг порта wiringPi  разрешающий регистр дисплея E (у нас 10)
    * int d0, int d1, int d2, int d3, int d4, int d5, int d6, int d7 - маппинг портов wiringPi на шину данных дисплея
  3. lcdPrintf(int handle, char *message, …) - в качестве int handle передаем указатель на дисплей, * message - указываем в кавычках текст для вывода
Теперь чуть подробнее про маппинг. В wiringPi используется понятие мапинга (привязки) физических пинов порта GPIO к "виртуальным" пинам - wiringPi. 
Таблица 3 с маппингом приведена ниже.
Сопоставив ее с таблицей 1 мы и получим комбинацию fd = lcdInit (2,8,4, 11,10, 1,0,2,3,0,0,0,0);
Пины int d4, int d5, int d6, int d7 в 4-х битном режиме не используются!
Таблица 3.

Итак, мы сохранили листинг с программой в файле "mylcd.c" в директории /home/pi/wiringPi/ (которая создается автоматически после установки пакета).
Далее для компиляции кода набираем в консоли:
gcc -Wall -o mylcd wiringPi/mylcd.c -lwiringPi -lwiringPiDev

Если не вывелось никаких "ворнингов" и "эрроров" - значит повезло программа написана верно!
Для запуска программы набираем в консоли:
sudo ./mylcd

Получаем диагностические сообщения, которые мы сами и выводим:
Raspberry Pi LCD test
Start LCD initialization...
GO!

Вывод в консоли:

Смотрим на результат:

UPDATE 25.01.2014

Недавно ко мне попал новый дисплей МЭЛТ MT–20S4A, который имеет 4 строки по 20 знаков в каждой.
Ниже привожу несколько фотографий процесса подключения данного дисплея к Raspberry Pi.





Таблица подключения проводников приведена на рисунке ниже.

Для вывода текста на дисплей 20x4 нам потребуется незначительно изменить код программы, листинг которой я привел ниже.
 
#include <stdio.h>  //стандартная библиотека ввода-вывода
#include <wiringPi.h>  //библиотека из пакета wiringPi
#include <lcd.h>  //библиотека из пакета wiringPi

int main (void)
{
 printf ("Raspberry Pi LCD test\n") ;
 //Инициализация порта GPIO
 if(wiringPiSetup ()==-1)
 {
  printf ("GPIO Setup failed!\n") ;
 }

 int fd;
 printf ("Start LCD initialization...\n") ;
 //Инициализация LCD
 fd = lcdInit (4,20,4, 11,10, 1,0,2,3,0,0,0,0);

 if(fd==-1)
 {
  printf ("Initialization failed\n") ;
 }
 else
 {
  printf ("GO!\n");

  //Очистка дислпея
  lcdClear(fd);
  
  //Перевод каретки на первую позицию первой строки
  lcdPosition (fd,0,0);
  
  //Вывод форматированного текста
  lcdPrintf(fd, "Hello Pi");

  //Перевод каретки на вторую строку и вывод текста
  lcdPosition (fd,0,1);
  lcdPrintf(fd, " World!");

  //Перевод каретки на третью строку и вывод текста
  lcdPosition (fd,0,2);
  lcdPrintf(fd, "25-01-2014");

  //Перевод каретки на четвертую строку и вывод текста
  lcdPosition (fd,0,3);
  lcdPrintf(fd, "Happy New Year))))");
 }
 return 0;
}
 

Как видно из листинга, в функции lcdInit() в двух первых аргументах теперь мы передаем количество строк = 4 и количество символов в строке = 20.
Далее мы дополнительно вызываем функции lcdPosition() и lcdPrintf() чтобы вывести текст на третью и четвертую строки.
Результат работы программы приведен на рисунке ниже.


Ниже привожу несколько фото дисплея в корпусе от DIR-320.








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

Похоже, что подключить LCD дисплей не так уж и трудно, если вы хоть раз держали в руках паяльник и клавиатуру.

Полезные ссылки

воскресенье, 1 сентября 2013 г.

Добавление изображения в документ Word через OpenXML

Разве это не очевидно? Чем мы всё чаще сталкиваемся с задачами, связанными с автоматизацией создания и редактирования "ms officных" документов, тем нам всё чаще приходит на ум название: Open XML SDK 2.0 for Microsoft Office. Сегодня речь пойдет об автоматизации вставки изображения в определенную область в документе формата MS Word (.docx).

Пример разработки приложения для вставки изображения в документ MS Word 

Определение области в документе для вставки изображения

Для того чтобы задать определенное место в документе, в которое будет осуществлена вставка изображения мы воспользуемся функционалом из вкладки "Разработчик". 

Если у вас данная вкладка не отображается, то включить ее можно в "Параметры Word" -> "Настройка ленты".

Далее в самом документе ставим курсор в необходимое место (в котором необходимо разместить рисунок), нажимаем на панели "Разработчик" кнопку: Элемент управления содержимым 'рисунок', как показано на примере ниже.

В результате в документе появится редактируемая область, размеры которой можно изменять. Данную область можно сразу заполнить рисунком, выбрав файл изображения из локальной папки. Но для наших целей мы оставим её пустой, немного изменив размер, используя маркеры.

Нас особо интересует возможность редактирования свойств данной области - "Название" и "Тег". Для этого выделите область и нажмите "Свойства" на панели "Разработчик". В поле "Название" мы укажем произвольное имя данной области, например - "Здесь будет картинка!". В поле "Тег" впишем особую метку для данной области, которую будем использовать далее, например - "рисунок".

Нажимаем "ОК" на панели свойств. Сохраняем документ. В итоге документ принимает вид как на рисунке ниже. 

Подготовка закончена!

Начало работы с Open XML SDK 2.0 for Microsoft Office

Для старта нам необходимо скачать и установить инсталляционный пакет Open XML SDK 2.0 for Microsoft Office из Download Center. После инсталляции пакета в папке установки Open XML SDK по пути Open XML SDK\V2.0\lib можно найти библиотеку DocumentFormat.OpenXml.dll, которой мы и воспользуемся.

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

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

Далее в проекте нужно добавить референс на библиотеку DocumentFormat.OpenXml.dll, которую для удобства можно скопировать в папку с проектом.

WordprocessingDocument

Для открытия документа Word из потока используем класс WordprocessingDocument, следующим образом:
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(myStream, false))
{
  foreach (var item in wordDoc.MainDocumentPart.Document.Body)
  {
      var oo = item.Descendants<SdtProperties>();
      foreach (var f1 in oo)
      {
          _contName = FindPictureContainer(wordDoc, f1, ref _sdtPropId);
      }
  }
}
В цикле foreach перебираем все элементы в теле документа Body. Далее для каждого элемента проверяем наличие тега <w:sdtPr>  и обозначающего: Structured Document Tag Properties. В WordprocessingDocument этот тег десериализуется в класс SdtProperties.  Этот элемент определяет набор свойств, которые должны применяться к родительскому тегу документа. Родительским тегом в нашем случае является тег <w:sdt>, в котором и содержится искомая область для вставки рисунка <w:drawing>.

 
  
  
  
  
  
 
 
  
   
    
   
   
    
     
     
    
    
     
      
      
      
      
       
      
      
       
        
         
          
          
           
          
         
         
          
           
            
             
            
           
          
          
          
           
          
         
         
          
           
           
          
          
           
          
          
          
           
          
         
        
       
      
     
    
   
  
 



Поиск области для вставки

Для поиска нужной области для вставки изображения воспользуемся свойством "Тег", значение которого мы определили как "рисунок" в предыдущем пункте. В OpenXML данному свойству соответствует тег <w:tag>, который десериализуется в класс Tag.
Также нас интересуют теги:

  • <w:alias>  он же SdtAlias, название области, которое мы задали в самом документе
  • <w:id> он же SdtId, ID области, является уникальным в рамках одного документа

В листинге, приведенном ниже, я описал метод, в котором осуществляется проверка соответствия тега в области значению - "рисунок", после чего возвращаются значения alias и id.
private string FindPictureContainer(WordprocessingDocument wdDoc, OpenXmlElement uy, ref string SdtId)
{
    SdtAlias alias = uy.Elements<SdtAlias>().FirstOrDefault();
    SdtId sdtId = uy.Elements<SdtId>().FirstOrDefault();
    Tag tag = uy.Elements<Tag>().FirstOrDefault();

    string _tag = "";
    string _sdtId = "";
    string _alias = "";

    //Получаем тег контейнера
    if (tag != null)
        _tag = tag.Val;

    //Получаем ID контейнера
    if (sdtId != null)
        SdtId = _sdtId = sdtId.Val;

    //Получаем название контейнера
    if (alias != null)
        _alias = alias.Val;

    if (_tag.Contains("рисунок"))
    {
        try
        {
            var sdtBlock = wdDoc.MainDocumentPart.Document.Descendants<SdtBlock>()
                        .Where(r => r.SdtProperties.GetFirstChild<SdtId>().Val == _sdtId);
            if (sdtBlock != null)
            {
                Drawing dr = sdtBlock.First().Descendants<Drawing>().FirstOrDefault();
                if (dr != null)
                {
                    GetSizeOfPictureContainer(dr, ref _contWidth, ref _contHeight);
                }
            }
        }
        catch (Exception ex)
        {
            ErrorHandler(ex);
        }
    }
    return _alias;
}

Описание метода GetSizeOfPictureContainer() привожу в листинге ниже. Этот метод определяет размеры области в документе для дальнейшего пропорционального масштабирования изображения при вставке.
private void GetSizeOfPictureContainer(Drawing d, ref int maxWidth, ref int maxHeight)
{
    Extent imageSizeProps = d.Descendants<Extent>().FirstOrDefault();
    maxWidth = (int)(imageSizeProps.Cx / 9525);
    maxHeight = (int)(imageSizeProps.Cy / 9525);
}

Пропорциональное изменение размеров изображения

Так как вставляемое изображение может быть больше или меньше области для вставки, то необходимо вычислить новые размеры для соблюдения пропорций.
В листинге ниже я привожу пример соответствующего метода:
private static void ResizePictureContainer(Drawing d, int originalWidth, int originalHeight, ref int maxWidth, ref int maxHeight)
{
    Extent imageSizeProps = d.Descendants<Extent>().FirstOrDefault();

    if (imageSizeProps != null)
    {
        int imageWidthOr = (int)(imageSizeProps.Cx / 9525);
        int imageHeightOr = (int)(imageSizeProps.Cy / 9525);
        maxWidth = imageWidthOr;
        maxHeight = imageHeightOr;
        //Определяем соотношение сторон
        double aspectRatio = (double)originalWidth / (double)originalHeight;

        //Проверяем, что высота изображения больше разрешенной высоты
        int newHeight = (originalHeight > imageHeightOr) ? imageHeightOr : originalHeight;
        //Проверяем, что ширина изображения больше разрешенной ширины
        int newWidth = (originalWidth > imageWidthOr) ? imageWidthOr : originalWidth;
        //Вычисляем новую высоту или ширину в зависимости от соотношения сторон (полагаясь на ориентацию изображения)
        if ((newWidth == originalWidth) && (newHeight == originalHeight))
        {
            //Если ширина больше, то ориентация книжная
            if (newWidth > newHeight)
            {
                //Вычисляем новую высоту умножением ширины на соотношение сторон
                newHeight = (int)(imageWidthOr / aspectRatio);
                newWidth = imageWidthOr;
                //в некторых случаях вычисленная высота может быть больше чем разрешенная 
                //поэтому нужно подвести высоту к разрешенной и пересчитать ширину
                if (newHeight > imageHeightOr)
                {
                    newHeight = imageHeightOr;
                    newWidth = (int)(aspectRatio * newHeight);
                }
            }
            else //ориентация портретная
            {
                //Вычисляем новую ширину умножением высоты на соотношение сторон
                newWidth = (int)(aspectRatio * imageHeightOr);
                newHeight = imageHeightOr;
            }
        }
        else //Если исходное изображение меньше, чем контейнер
        {
            if (newWidth > newHeight)
            {
                newHeight = (int)(newWidth / aspectRatio);
                if (newHeight > imageHeightOr)
                {
                    newHeight = imageHeightOr;
                    newWidth = (int)(aspectRatio * newHeight);
                }
            }
            else 
            {
                newWidth = (int)(aspectRatio * newHeight);
            }
        }
        imageSizeProps.Cx = (long)(newWidth * 9525);
        imageSizeProps.Cy = (long)(newHeight * 9525);
    }

    Extents e2 = d.Descendants<Extents>().FirstOrDefault();

    long imageWidthEmu = (long)(originalWidth * 9525);
    long imageHeightEmu = (long)(originalHeight * 9525);
    if (e2 != null)
    {
        e2.Cx = imageWidthEmu;
        e2.Cy = imageHeightEmu;
    }
}

Вставка изображения

Для вставки изображения нужно обратить внимание на два метода:

  • wordprocessingDocument.MainDocumentPart.AddImagePart()
  • ImagePart.FeedData()
Листинг с примером приведен ниже:
ImagePart ip = wordprocessingDocument.MainDocumentPart.AddImagePart(ImagePartType.Jpeg);
using (Stream inStream = openFileDialog2.OpenFile())
{            
 inStream.Position = 0;
 ip.FeedData(inStream);
}

Всеь проект для Visual Studio 2010 можно скачать по ссылке.

Результат работы программы

После запуска программы выбираем подготовленный документ MS Word из локальной папки, выбираем изображение для вставки и сохраняем результат.

Открываем документ и проверяем изображение.

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

Вставкой изображений не исчерпываются возможности работы с документами MS Word через OpenXML. О других применениях мы поговорим в других статьях!

понедельник, 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