Please read this post to get a brief explanation of the scenario I will implement in Microsoft Dynamics NAV 2009 SP1. Please also read this post in order to understand how Web Services works using pure XML and no fancy objects.

Like Javascript, NAV 2009 SP1 does not natively have support for consuming Web Services. It does however have support for both Client and Server side COM automation and XmlHttp (which is compatible with XmlHttpRequest which we used in the Javascript sample here) is available in Microsoft XML.

Client or Serverside

When running XmlHttp under the RoleTailored Client we have to determine whether we want to run XmlHttp serverside or clientside. My immediate selection was Serverside (why in earth do this Clientside) until I tried it out and found that my server was not allowed to impersonate me against a web services which again needs to impersonate my against the database.

The double hub issue becomes a triple hub issue and now it suddenly becomes clear that XmlHttp in this sample of course needs to run clientside:-)

Compatibility

The sample below will run both in the RoleTailored Client and in the Classic Client.

InvokeNavWS

As in the Javascript example, I will create a function called InvokeNavWS – and in this function I will do the actual Web Service method invocation. In Javascript we setup an event to be called when the send method was done and as you might know, this is not doable on the Roletailored Client.

Fortunately, we are using synchronous web services, meaning that it is actually not necessary to setup this event. We can just check the status when send returns.

xmlhttp.send allows you to send either a string or a XML Document. Having in mind that a string in NAV Classic is max. 1024 characters, I decided to go with a XML Document. In the RoleTailored Client I could have used BigText, but that doesn’t work in Classic.

Creating a XML Document will take slightly more time than building up a large string, but it is the safest way to go. Start by adding an Envelope, a body, a method and then transfer the parameter nodes one by one (there might be smarter ways to do this:-)

The return value is always a nodeList and we only look at the responseXML property of the xmlhttp (which is an XML document).

The Code for InvokeNavWS looks like this:

InvokeNavWS(URL : Text[250];method : Text[20];nameSpace : Text[80];returnTag : Text[20];parameters : Text[1024];VAR nodeList : Automation "'Microsoft XML, v6.0'.IXMLDOMNodeList") result : Boolean
result := FALSE;
// Create XML Document
CREATE(xmldoc,TRUE,TRUE);
// Create SOAP Envelope
soapEnvelope := xmldoc.createElement('Soap:Envelope');
soapEnvelope.setAttribute('xmlns:Soap', '
http://schemas.xmlsoap.org/soap/envelope/');
xmldoc.appendChild(soapEnvelope);
// Create SOAP Body
soapBody := xmldoc.createElement('Soap:Body');
soapEnvelope.appendChild(soapBody);
// Create Method Element
soapMethod := xmldoc.createElement(method);
soapMethod.setAttribute('xmlns', nameSpace);
soapBody.appendChild(soapMethod);
// Transfer parameters by loading them into a XML Document and move them
CREATE(parametersXmlDoc,TRUE,TRUE);
parametersXmlDoc.loadXML('<parameters>'+parameters+'</parameters>');
IF parametersXmlDoc.firstChild.hasChildNodes THEN
BEGIN
  WHILE parametersXmlDoc.firstChild.childNodes.length>0 DO
  BEGIN
    node := parametersXmlDoc.firstChild.firstChild;
    node := parametersXmlDoc.firstChild.removeChild(node);
    soapMethod.appendChild(node);
  END;
END;
// Create XMLHTTP and SEND
CREATE(xmlhttp, TRUE, TRUE);
xmlhttp.open('POST', URL, FALSE);
xmlhttp.setRequestHeader('Content-type', 'text/xml; charset=utf-8');
xmlhttp.setRequestHeader('SOAPAction', method);
xmlhttp.send(xmldoc);
// If status is OK - Get Result XML
IF xmlhttp.status=200 THEN
BEGIN
  xmldoc := xmlhttp.responseXML;
  xmldoc.setProperty('SelectionLanguage','XPath');
  xmldoc.setProperty('SelectionNamespaces','xmlns:tns="'+nameSpace+'"');
  nodeList := xmldoc.selectNodes('//tns:'+returnTag);
  result := TRUE;
END;

and the local variables for InvokeNavWS are

Name              DataType      Subtype                                 Length
xmlhttp           Automation    'Microsoft XML, v6.0'.XMLHTTP   
xmldoc            Automation    'Microsoft XML, v6.0'.DOMDocument   
soapEnvelope      Automation    'Microsoft XML, v6.0'.IXMLDOMElement   
soapBody          Automation    'Microsoft XML, v6.0'.IXMLDOMElement   
soapMethod        Automation    'Microsoft XML, v6.0'.IXMLDOMElement   
node              Automation    'Microsoft XML, v6.0'.IXMLDOMNode   
parametersXmlDoc  Automation    'Microsoft XML, v6.0'.DOMDocument   

As in the Javascript sample I have create a couple of “high” level functions for easier access:

SystemService_Companies(VAR nodeList : Automation "'Microsoft XML, v6.0'.IXMLDOMNodeList") result : Boolean
result := InvokeNavWS(systemServiceURL, 'Companies', SystemServiceNS, 'return_value', '', nodeList);

CustomerPage_Read(No : Text[20];VAR nodeList : Automation "'Microsoft XML, v6.0'.IXMLDOMNodeList") result : Boolean
result := InvokeNavWS(customerPageURL, 'Read', CustomerServiceNS, 'Customer', '<No>'+No+'</No>', nodeList);

CustomerPage_ReadMultiple(filters : Text[1024];VAR nodeList : Automation "'Microsoft XML, v6.0'.IXMLDOMNodeList") result : Boolean
result := InvokeNavWS(customerPageURL, 'ReadMultiple', CustomerServiceNS, 'Customer', filters, nodeList);

The “main” program

OnRun()
baseURL := '
http://localhost:7047/DynamicsNAV/WS/';
systemServiceURL := baseURL + 'SystemService';
SoapEnvelopeNS := '
http://schemas.xmlsoap.org/soap/envelope/';
SystemServiceNS := 'urn:microsoft-dynamics-schemas/nav/system/';
CustomerServiceNS := 'urn:microsoft-dynamics-schemas/page/customer';

CLEAR(nodeList);
IF SystemService_Companies(nodeList) THEN
BEGIN
  DISPLAY('Companies:');
  FOR i:=1 TO nodeList.length DO
  BEGIN
    node := nodeList.item(i-1);
    DISPLAY(node.text);
    IF i=1 THEN cur := node.text;
  END;

  customerPageURL := baseURL + EncodeURIComponent(cur) + '/Page/Customer';
  DISPLAY('URL of Customer Page: '+ customerPageURL);

  IF CustomerPage_Read('10000', nodeList) THEN
  BEGIN
    DISPLAY('Name of Customer 10000: ' + nodeList.item(0).childNodes.item(2).firstChild.text);
  END;

  IF CustomerPage_ReadMultiple('<filter><Field>Country_Region_Code</Field><Criteria>GB</Criteria></filter>'+
'<filter><Field>Location_Code</Field><Criteria>RED|BLUE</Criteria></filter>', nodeList) THEN
  BEGIN
    DISPLAY('Customers in GB served by RED or BLUE warehouse:');
    FOR i:=1 TO nodeList.length DO
    BEGIN
      node := nodeList.item(i-1);
      DISPLAY(node.childNodes.item(2).firstChild.text);
    END;
  END;

  DISPLAY('THE END');

END;

with the following local variables:

Name       DataType      Subtype                                 Length
nodeList   Automation    'Microsoft XML, v6.0'.IXMLDOMNodeList   
node       Automation    'Microsoft XML, v6.0'.IXMLDOMNode   
i          Integer       

As it was the case in the Javascript sample I am using simple xml nodelist code to navigate and display various values. baseURL, cur, SystemServiceURL etc. are all global Text variables used as constants.

DISPLAY points to a function that just does a IF CONFIRM(s) THEN ; to display where we are and running this on the RoleTailored Client will display the following Confirm Dialogs:

image image image

image

image

image

image image image image

image

Note that the URL of the Customer Page is different from all the other examples. This is because NAV doesn’t have a way of Encoding an URL, so I have to do the company name encoding myself and when I encode a company name, I just encode all characters, that works perfectly:

EncodeURIComponent(uri : Text[80]) encodedUri : Text[240]
// No URI Encoding in NAV - we do it ourself...
HexDigits := '0123456789ABCDEF';
encodedUri := '';
FOR i:=1 TO STRLEN(uri) DO
BEGIN
  b := uri[i];
  encodedUri := encodedUri + '%  ';
  encodedUri[STRLEN(encodedUri)-1] := HexDigits[(b DIV 16)+1];
  encodedUri[STRLEN(encodedUri)] := HexDigits[(b MOD 16)+1];
END;

(Again, there might be smarter ways to do this – I just haven’t found it).

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV