Подключение к веб-сервисам NAV из Javascript

Предыстория

Пожалуйста, ознакомьтесь с этой статьей, чтобы получить представление о работе будущего скрипта на Javascript.
Для понимания заметки требуется базовые знания Javascript и XML :)

Совместимость с браузерами

Указанный в данной статье пример, будет работать с браузером Internet Explorer. Тестирование выполнялось только в браузере IE8, но, в соответствии с документацией, код должен корректно работать в браузере IE5 и выше.

На самом деле я также пробовал выполнять скрипт в браузерах Mozilla FireFox 3.6 и Opera 10.10, но безрезультатно. Причина неудачи была не из-за несовместимости Javascript, а в том, что с указанными браузерами не удалось получить авторизацию к веб-сервисам NAV (независимо от того использовал я способ авторизации NTLM или SPNEGO – см. эту статью).

Данная ситуация может быть обусловлена тем, что XmlHttpRequest не в состоянии подключиться к Web Services X-Domain (объяснение – здесь) – если Вам все же необходимо чтобы скрипт работал в указанных браузерах, то возможным решением будет установить прокси-сервер, который нужно разместить на том же сайте, что и Javascript-приложение и перенаправлять все поступающие запросы.

Браузер IE, кажется, не волнует, если у вас есть доверенный веб-сайт (о котором здесь идет речь). Скрипт, получающий доступ к веб-сервисам, в любом случае должен быть использован только в локальной сети – Вам никогда не стоит пытаться подключаться к NAV–серверу через Интернет.

Другие статьи о коде Javascript

В блоге я опубликовывал множество статей, где я использую Javascript для подключения к веб-службам. Все Гаджеты и Virtual Earth веб-сайт, используют Javascript для подключения к веб-сервисам и во всех из них я использовал MSXML ActiveXObject, в остальных случаях доступ выполняется через Кодеюнит. В этом примере я покажу, как это можно сделать без использования ActiveX и использования страницы.

Это все об XML

Javascript изначально не предоставляет средств для создания строго типизированных прокси классов, по сравнению с такими языками программирования как C#, VB и Java, и не «умеет» обмениваться данными с веб-сервисами через SOAP-протокол, как например PHP – это всё про XML.

В конечном итоге, принцип взаимодействия с веб-сервисом основан на создании запроса в виде XML-документа (в основном в виде простой строки), который по сети отправляется хосту и обратном получении ответа в виде всё того же XML-документа (опять-таки только отформатированную строку).

Обработка строк возможна в Javascript и у Javascript имеется объект под названием XmlHttpRequest, который способен взаимодействовать с хостом веб-сервиса используя для этого XML.

NAV 2009 (и SP1) взаимодействует с веб-сервисами, используя SOAP-протокол. С основами протокола можно ознакомиться здесь: http://en.wikipedia.org/wiki/SOAP.

В указанном ниже сценарии, обращение к веб-сервисам происходит 3 раза:

1. Получение компаний, поддерживаемых веб-сервисом;
2. Получение определенного клиента;
3. Получение всех клиентов, соответствующих определенному фильтру.

Получение компаний, поддерживаемых веб-сервисом

В C #, мы создаём ссылку на SystemService, благодаря чему затем создаются некоторые прокси-классы, и мы, используя эти классы, только вызываем метод под названием Companies.

Под этой магией, .net создаст XML-строку, которая выглядит следующим образом:

<Soap:Envelope xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
  <Soap:Body>
    <Companies xmlns="urn:microsoft-dynamics-schemas/nav/system/">
    </Companies>
  </Soap:Body>
</Soap:Envelope>

Как Вы можете заметить, единственное, что я на самом деле отправил это (с пространством имен).
Значение, возвращаемое методом Companies:

<Soap:Envelope xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
  <Soap:Body>
    <Companies_Result xmlns="urn:microsoft-dynamics-schemas/nav/system/">
      <return_value>CRONUS International Ltd.</return_value>
      <return_value>TEST</return_value>
    </Companies_Result>
  </Soap:Body>
</Soap:Envelope>

В следующей статье я покажу как, используя .NET, просматривать отправляемые и получаемые XML – строки.

Получение определенного Клиента

XML для получения определенного клиента выглядит следующим образом:

<Soap:Envelope xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
  <Soap:Body>
    <Read xmlns="urn:microsoft-dynamics-schemas/page/customer">
      <No>10000</No>
    </Read>
  </Soap:Body>
</Soap:Envelope>

а возвращаемая из NAV XML-строка с данными из карточки Клиента:

<Soap:Envelope xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
  <Soap:Body>
    <Read_Result xmlns="urn:microsoft-dynamics-schemas/page/customer">
      <Customer>
        <Key>... some huge key ...</Key>
        <No>10000</No>
        <Name>The Cannon Group PLC</Name>
        <Address>192 Market Square</Address>
        <Address_2>Address no. 2</Address_2>
        ... all the other fields ...
      </Customer>
    </Read_Result>
  </Soap:Body>
</Soap:Envelope>

Я не перечислял все поля — вы, вероятно, получите полную картину.

Получение всех Клиентов, соответствующих определенному фильтру

XML для получения всех клиентов, удовлетворяющих определенному фильтру, выглядит следующим образом:

<Soap:Envelope xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
  <Soap:Body>
    <ReadMultiple xmlns="urn:microsoft-dynamics-schemas/page/customer">
      <filter><Field>Country_Region_Code</Field><Criteria>GB</Criteria></filter>
      <filter><Field>Location_Code</Field><Criteria>RED|BLUE</Criteria></filter>
    </ReadMultiple>
  </Soap:Body>
</Soap:Envelope>

и возвращаемый XML выглядит примерно так:

<Soap:Envelope xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
  <Soap:Body>
    <ReadMultiple_Result xmlns="urn:microsoft-dynamics-schemas/page/customer">
    <ReadMultiple_Result>
      <Customer>
      ... one customer ...
      </Customer>
      <Customer>
      ... another customer ...
      </Customer>
      <Customer>
      ... a third customer ...
      </Customer>
    </ReadMultiple_Result>
    </ReadMultiple_Result>
  </Soap:Body>
</Soap:Envelope>

Достаточно о XML – давайте рассмотрим код

Вместо текста скрипта — я покажу здесь весь код html-страницы и ниже сделаю кой-какие пояснения.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
    var baseURL = 'http://localhost:7047/DynamicsNAV/WS/';
    var cur;
    var SystemServiceURL = baseURL + 'SystemService';
    var CustomerPageURL;
    var SoapEnvelopeNS = 'http://schemas.xmlsoap.org/soap/envelope/';
    var SystemServiceNS = 'urn:microsoft-dynamics-schemas/nav/system/';
    var CustomerPageNS = 'urn:microsoft-dynamics-schemas/page/customer';
// Функция для обращения к веб-сервису NAV и возвращающая данные из определенного Тега в responseXML
    function InvokeNavWS(URL, method, nameSpace, returnTag, parameters) {
        var result = null;
        try {
            var xmlhttp;
            if (window.XMLHttpRequest) {// код для IE7+, Firefox, Chrome, Opera, Safari
                xmlhttp = new XMLHttpRequest();
            }
            else {// код для IE6, IE5
                xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
            var request = '<Soap:Envelope xmlns:Soap="' + SoapEnvelopeNS + '">' +
                          '<Soap:Body>' +
                          '<' + method + ' xmlns="' + nameSpace + '">' +
                          parameters +
                          '</' + method + '>' +
                          '</Soap:Body>' +
                          '</Soap:Envelope>';
            // Используем метод Post и non-async
            xmlhttp.open('POST', URL, false);
            xmlhttp.setRequestHeader('Content-type', 'text/xml; charset=utf-8');
            xmlhttp.setRequestHeader('Content-length', request.length);
            xmlhttp.setRequestHeader('SOAPAction', method);
            // Устанавливаем обработчик состояния запроса
            xmlhttp.onreadystatechange = function() {
                if (xmlhttp.readyState == 4) {
                    if (xmlhttp.status == 200) {
                        xmldoc = xmlhttp.responseXML;
                        xmldoc.setProperty('SelectionLanguage', 'XPath');
                        xmldoc.setProperty('SelectionNamespaces', 'xmlns:tns="' + nameSpace + '"');
                        result = xmldoc.selectNodes('//tns:' + returnTag);
                    }
                }
            }
            // Отправленный запрос возвратит данные когда состояние запроса readyState 4
            xmlhttp.send(request);
        }
        catch (e) {
        }
        return result;
    }
    // Получение списка Компаний
    function SystemService_Companies() {
        return InvokeNavWS(SystemServiceURL, 'Companies', SystemServiceNS, 'return_value', '');
    }
    function CustomerPage_Read(no) {
        return InvokeNavWS(CustomerPageURL, 'Read', CustomerPageNS, 'Customer',
                           '<No>' + no + '</No>');
    }
    function CustomerPage_ReadMultiple(filters) {
        return InvokeNavWS(CustomerPageURL, 'ReadMultiple', CustomerPageNS, 'Customer', filters);
    }
</script>
</head>  
<body>
<script type="text/javascript">
    var companies = SystemService_Companies();
    document.writeln('Companies:<br>');
    for (var i = 0; i < companies.length; i++) {
       document.writeln(companies[i].text + '<br>');
    }
    cur = companies[0].text;
    CustomerPageURL = baseURL + encodeURIComponent(cur) + '/Page/Customer';
    document.writeln('<br>URL of Customer Page: ' + CustomerPageURL + '<br>');
    var Customer10000 = CustomerPage_Read('10000');
    document.writeln('<br>Name of Customer 10000: ' +
                     Customer10000[0].childNodes[2].firstChild.nodeValue + '<br>');
    document.writeln('<br>Customers in GB served by RED or BLUE warehouse:<br>');
    var Customers = CustomerPage_ReadMultiple(
                    '<filter><Field>Country_Region_Code</Field><Criteria>GB</Criteria></filter>'+
                    '<filter><Field>Location_Code</Field><Criteria>RED|BLUE</Criteria></filter>');
    for (i = 0; i < Customers.length; i++)
       document.writeln(Customers[i].childNodes[2].firstChild.nodeValue + '<br>');
    document.writeln('<br>THE END');
</script>
</body>
</html>

Это весь код Default.htm файла.

Большая часть «магии» происходит внутри метода InvokeNavWS, который, на самом деле, просто создает XML-документ, указанный в методе, пространстве имен и параметров метода. Этот XML-документ затем отправляется по адресу URL, полученный XML-документ мы считываем в XMLDoc и используем XPath для получения возвращаемого значения (ReturnTag определяет, какие теги нас интересуют).

В дополнение к этому методу, я создал несколько высокоуровневых функций, для того чтобы мы могли вызвать CustomerPage_Read(‘10000’) и получить обратно данные клиента.

Обратите внимание, что Read и ReadMultiple возвращают XML NodeList (коллекцию узлов) – метод ChildNodes, возвращает содержание всех дочерних элементов родительского узла (т.е. это наши поля) и для получения значения поля используйте firstChild.nodeValue.

В этом примере я жестко указал название поля, которое будет в ChildNodes[2], хотя это безусловно не самый лучший способ для получения названия — но опять же, это только чтобы показать, как получить работающее подключение.

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

Надеюсь, статья будет вам полезна.

Удачи!

Оригинал заметки расположен здесь: http://blogs.msdn.com/b/freddyk/archive/2010/01/21/connecting-to-nav-web-services-from-javascript.aspx

Автор:

Количество статей, опубликованных автором: 3. Дополнительная информация об авторе появится вскоре.

Добавить комментарий