![]() |
#1 |
Участник
|
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: ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() 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 Подробнее
__________________
Расскажите о новых и интересных блогах по Microsoft Dynamics, напишите личное сообщение администратору. |
|