Подключение к веб-сервисам 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:Body>
<Companies xmlns="urn:microsoft-dynamics-schemas/nav/system/">
</Companies>
</Soap:Body>
</Soap:Envelope>
Как Вы можете заметить, единственное, что я на самом деле отправил это
Значение, возвращаемое методом Companies:
<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:Body>
<Read xmlns="urn:microsoft-dynamics-schemas/page/customer">
<No>10000</No>
</Read>
</Soap:Body>
</Soap:Envelope>
а возвращаемая из NAV XML-строка с данными из карточки Клиента:
<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: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: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-страницы и ниже сделаю кой-какие пояснения.
<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. Дополнительная информация об авторе появится вскоре.