AL and SOAP
This sample is based on a page extension for page 8 (Currencies) where an action for downloading exchange rates from the Swedish State Bank has been added. The logic itself isn’t super advanced and it requires SEK as local currency. The focus here is to show how to:
- Create SOAP request XML
- Do an HTTP Post using httpclient
- Read the XML in the response and do something with that.
I have used a codeunit for XML DOM management created by Divesh Bora which makes things a lot easier.
I hope you find this useful and helps you on your way to move from C/AL to AL.
pageextension 57900 CurrencyListExtension extends Currencies { actions { addafter(UpdateExchangeRates) { action(DownloadExchRates) { ApplicationArea = All; CaptionML = ENU = 'Get Exchange Rates from State Bank of Sweden', SVE = 'Hämta valutakurser från Sveriges Riksbank'; Image = CurrencyExchangeRates; Promoted = true; PromotedCategory = Category4; trigger OnAction(); var DownloadExchRates: codeunit "SE State Bank Exch Rates"; TxtDownloadSucceeded: TextConst ENU = 'Exchange Rates updated successfully', SVE = 'Valutakurserna har uppdateras'; JobQueueEntry: Record "Job Queue Entry"; begin if DownloadExchRates.Run(JobQueueEntry) then message(TxtDownloadSucceeded); end; } } } }
codeunit 57900 "SE State Bank Exch Rates" { // https://swea.riksbank.se/sweaWS/docs/api/call/getLatestInterestAndExchangeRates.htm // https://swea.riksbank.se/sweaWS/wsdl/sweaWS_ssl.wsdl TableNo = "Job Queue Entry"; var XMLDOMMgmt: Codeunit "XML DOM Mgt."; SoapNS : Label 'http://www.w3.org/2003/05/soap-envelope'; XsdNS : Label 'http://swea.riksbank.se/xsd'; URL: label 'https://swea.riksbank.se:443/sweaWS/services/SweaWebServiceHttpSoap12Endpoint'; NsMgr : XmlNamespaceManager; trigger OnRun(); var GLSetup : Record "General Ledger Setup"; XMLDoc : XmlDocument; Currency : Record Currency; CurrNode : XmlNode; CurrNode2 : XmlNode; CurrNodeList : XmlNodeList; CurrExchRate : Record "Currency Exchange Rate"; Unit : Decimal; DebugFile: File; OutStr: OutStream; NsMgr: XmlNamespaceManager; begin GLSetup.GET; GLSetup.TESTFIELD("LCY Code", 'SEK'); if Currency.FINDSET then with XMLDOMMgmt do begin AddRootElementWithPrefix(XMLDoc, 'Envelope', 'soap', SoapNS, CurrNode2); SetUtf8Declaration(XMLDoc); CurrNode := CurrNode2; AddPrefix(CurrNode, 'xsd', XsdNS); CurrNode := CurrNode2; AddElement(CurrNode, 'Header', '', SoapNs, CurrNode2); CurrNode := CurrNode2; AddElement(Currnode, 'Body', '', SoapNs, CurrNode2); CurrNode := CurrNode2; AddElement(Currnode, 'getLatestInterestAndExchangeRates', '', XsdNS, CurrNode2); CurrNode := CurrNode2; if GLOBALLANGUAGE = 1053 then AddElement(CurrNode, 'languageid', 'sv', '', CurrNode2) else AddElement(CurrNode, 'languageid', 'en', '', CurrNode2); repeat AddElement(CurrNode,'seriesid', GLSetup."LCY Code" + Currency.Code + 'PMI', '', CurrNode2); until Currency.NEXT = 0; HTTPPost(XMLDoc, URL); GetRootNode(XMLDoc, CurrNode); AddNameSpace(NsMgr, 'soap', SoapNS); AddNameSpace(NsMgr, 'ns2', XsdNS); if FindNodesWithNamespaceManager(CurrNode, '//soap:Body/ns2:getLatestInterestAndExchangeRatesResponse/return/groups/series', NsMgr, CurrNodeList) then foreach CurrNode in CurrNodeList do with CurrExchRate do begin INIT; if FindNode(CurrNode, 'seriesid', CurrNode2) then "Currency Code" := COPYSTR(CurrNode2.AsXmlElement.InnerText, 4, 3); if FindNode(CurrNode, 'unit', CurrNode2) then Unit := FormatDecmialTextImport(CurrNode2.AsXmlElement.InnerText); "Relational Exch. Rate Amount" := 1; "Relational Adjmt Exch Rate Amt" := 1; if FindNode(CurrNode, 'resultrows', CurrNode2) then begin CurrNode := CurrNode2; if FindNode(CurrNode, 'date', CurrNode2) then "Starting Date" := FormatDateTextImport(CurrNode2.AsXmlElement.InnerText); if FindNode(CurrNode, 'value', CurrNode2) then begin "Exchange Rate Amount" := FormatDecmialTextImport(CurrNode2.AsXmlElement.InnerText); "Exchange Rate Amount" := 1 / "Exchange Rate Amount" * Unit; "Adjustment Exch. Rate Amount" := "Exchange Rate Amount"; if not INSERT then MODIFY; end; end; end; end; end; local procedure HTTPPost(var XmlDoc : XmlDocument;URL : Text[250]) : Boolean; var MyHTTPClient: HttpClient; RequestMessage: HttpRequestMessage; ResponseMessage: HttpResponseMessage; Headers: HttpHeaders; Content: HttpContent; CurrNodeList : XmlNodeList; XML_text: text; InStr: InStream; OutStr: OutStream; begin RequestMessage.SetRequestUri(URL); RequestMessage.Method('POST'); XmlDoc.WriteTo(XML_text); Content.WriteFrom(XML_text); Content.GetHeaders(Headers); Headers.Remove('Content-Type'); Headers.Add('Content-Type', 'application/soap+xml;charset=UTF-8;action="urn:getLatestInterestAndExchangeRates"'); RequestMessage.Content := Content; MyHTTPClient.send(RequestMessage, ResponseMessage); if not ResponseMessage.IsSuccessStatusCode then error(format(ResponseMessage.HttpStatusCode) +' , ' + ResponseMessage.ReasonPhrase); if ResponseMessage.IsSuccessStatusCode then begin ResponseMessage.Content.ReadAs(InStr); XMLDOMMgmt.LoadXMLDocumentFromInStream(InStr, XmlDoc); XMLDOMMgmt.SetUtf8Declaration(XmlDoc); end else error(format(ResponseMessage.HttpStatusCode)); end; local procedure FormatDecmialTextImport(TextDec : Text[30]) : Decimal; var TempDec : Decimal; begin if TextDec = '' then exit(0) else begin TempDec := 0.01; EVALUATE(TempDec, CONVERTSTR(TextDec, '.', COPYSTR(FORMAT(TempDec),2,1))); exit(TempDec); end; end; local procedure FormatDateTextImport(DateText : Text) : Date; var MyYear : Integer; MyMonth : Integer; MyDay : Integer; begin EVALUATE(MyYear, COPYSTR(DateText, 1, 4)); EVALUATE(MyMonth, COPYSTR(DateText, 6, 2)); EVALUATE(MyDay, COPYSTR(DateText, 9, 2)); exit(DMY2DATE(MyDay, MyMonth, MyYear));; end; }
codeunit 57901 "XML DOM Mgt." { https://diveshboramsdnavblog.wordpress.com/2018/03/09/vs-code-xml-dom-management-part-2/ trigger OnRun(); begin end; procedure AddElement(var pXMLNode: XmlNode; pNodeName: Text; pNodeText: Text; pNameSpace: Text; var pCreatedNode: XmlNode): Boolean begin IF pNodeText <> '' then pCreatedNode := XmlElement.Create(pNodeName, pNameSpace, pNodeText).AsXmlNode else pCreatedNode := XmlElement.Create(pNodeName, pNameSpace).AsXmlNode; Exit(pXMLNode.AsXmlElement.Add(pCreatedNode)); end; procedure AddRootElement(var pXMLDocument: XmlDocument; pNodeName: Text; var pCreatedNode: XmlNode): Boolean begin pXMLDocument := XmlDocument.Create; pCreatedNode := XmlElement.Create(pNodeName).AsXmlNode; Exit(pXMLDocument.Add(pCreatedNode)); end; procedure AddRootElementWithPrefix(var pXMLDocument: XmlDocument; pNodeName: Text; pPrefix: Text; pNameSpace: text; var pCreatedNode: XmlNode): Boolean begin pXMLDocument := XmlDocument.Create; pCreatedNode := XmlElement.Create(pNodeName, pNameSpace).AsXmlNode; pCreatedNode.AsXmlElement.Add(XmlAttribute.CreateNamespaceDeclaration(pPrefix, pNameSpace)); Exit(pXMLDocument.Add(pCreatedNode)); end; procedure AddElementWithPrefix(var pXMLNode: XmlNode; pNodeName: Text; pNodeText: Text; pPrefix: Text; pNameSpace: text; var pCreatedNode: XmlNode): Boolean begin IF pNodeText <> '' then pCreatedNode := XmlElement.Create(pNodeName, pNameSpace, pNodeText).AsXmlNode else pCreatedNode := XmlElement.Create(pNodeName, pNameSpace).AsXmlNode; pCreatedNode.AsXmlElement.Add(XmlAttribute.CreateNamespaceDeclaration(pPrefix, pNameSpace)); exit(pXMLNode.AsXmlElement.Add(pCreatedNode)); end; procedure AddPrefix(var pXMLNode: XmlNode; pPrefix: Text; pNameSpace: text): Boolean begin pXMLNode.AsXmlElement.Add(XmlAttribute.CreateNamespaceDeclaration(pPrefix, pNameSpace)); exit(true); end; procedure AddAttribute(var pXMLNode: XmlNode; pName: Text; pValue: Text): Boolean begin pXMLNode.AsXmlElement.SetAttribute(pName, pValue); exit(true); end; procedure AddAttributeWithNamespace(var pXMLNode: XmlNode; pName: Text; pNameSpace: text; pValue: Text): Boolean begin pXMLNode.AsXmlElement.SetAttribute(pName, pNameSpace, pValue); exit(true); end; procedure FindNode(pXMLRootNode: XmlNode; pNodePath: Text; var pFoundXMLNode: XmlNode): Boolean begin exit(pXMLRootNode.SelectSingleNode(pNodePath, pFoundXMLNode)); end; procedure GetRootNode(pXmlDoc: XmlDocument; var pFoundXMLNode: XmlNode): Boolean var lXmlElement : XmlElement; begin pXMLDoc.GetRoot(lXmlElement); pFoundXMLNode := lXmlElement.AsXmlNode; end; procedure FindNodeWithNameSpace(pXMLRootNode: XmlNode; pNodePath: Text; pPrefix: Text; pNamespace: Text; var pFoundXMLNode: XmlNode): Boolean var lXmlNsMgr: XmlNamespaceManager; lXMLDocument: XmlDocument; begin if pXMLRootNode.IsXmlDocument then lXmlNsMgr.NameTable(pXMLRootNode.AsXmlDocument.NameTable) else begin pXMLRootNode.GetDocument(lXMLDocument); lXmlNsMgr.NameTable(lXMLDocument.NameTable); end; lXMLNsMgr.AddNamespace(pPrefix, pNamespace); exit(pXMLRootNode.SelectSingleNode(pNodePath, lXmlNsMgr, pFoundXMLNode)); end; procedure FindNodesWithNameSpace(pXMLRootNode: XmlNode; pXPath: Text; pPrefix: Text; pNamespace: Text; var pFoundXmlNodeList: XmlNodeList): Boolean var lXmlNsMgr: XmlNamespaceManager; lXMLDocument: XmlDocument; begin if pXMLRootNode.IsXmlDocument then lXmlNsMgr.NameTable(pXMLRootNode.AsXmlDocument.NameTable) else begin pXMLRootNode.GetDocument(lXMLDocument); lXmlNsMgr.NameTable(lXMLDocument.NameTable); end; lXMLNsMgr.AddNamespace(pPrefix, pNamespace); exit(FindNodesWithNamespaceManager(pXMLRootNode, pXPath, lXmlNsMgr, pFoundXmlNodeList)); end; procedure FindNodesWithNamespaceManager(pXMLRootNode: XmlNode; pXPath: Text; pXmlNsMgr: XmlNamespaceManager; var pFoundXmlNodeList: XmlNodeList): Boolean begin IF not pXMLRootNode.SelectNodes(pXPath, pXmlNsMgr, pFoundXmlNodeList) then exit(false); IF pFoundXmlNodeList.Count = 0 then exit(false); exit(true); end; procedure FindNodeXML(pXMLRootNode: XmlNode; pNodePath: Text): Text var lFoundXMLNode: XmlNode; begin IF pXMLRootNode.SelectSingleNode(pNodePath, lFoundXMLNode) then Exit(lFoundXMLNode.AsXmlElement.InnerXml); end; procedure FindNodeText(pXMLRootNode: XmlNode; pNodePath: Text): Text var lFoundXMLNode: XmlNode; begin IF pXMLRootNode.SelectSingleNode(pNodePath, lFoundXMLNode) then Exit(lFoundXMLNode.AsXmlElement.InnerText); end; procedure FindNodeTextWithNameSpace(pXMLRootNode: XmlNode; pNodePath: Text; pPrefix: Text; pNamespace: Text): Text var lXmlNsMgr: XmlNamespaceManager; lXMLDocument: XmlDocument; begin if pXMLRootNode.IsXmlDocument then lXmlNsMgr.NameTable(pXMLRootNode.AsXmlDocument.NameTable) else begin pXMLRootNode.GetDocument(lXMLDocument); lXmlNsMgr.NameTable(lXMLDocument.NameTable); end; lXMLNsMgr.AddNamespace(pPrefix, pNamespace); Exit(FindNodeTextNs(pXMLRootNode, pNodePath, lXmlNsMgr)); end; procedure FindNodeTextNs(pXMLRootNode: XmlNode; pNodePath: Text; pXmlNsMgr: XmlNamespaceManager): Text var lFoundXMLNode: XmlNode; begin IF pXMLRootNode.SelectSingleNode(pNodePath, pXmlNsMgr, lFoundXMLNode) then Exit(lFoundXMLNode.AsXmlElement.InnerText); end; procedure FindNodes(pXMLRootNode: XmlNode; pNodePath: Text; var pFoundXMLNodeList: XmlNodeList): Boolean begin IF not pXMLRootNode.SelectNodes(pNodePath, pFoundXmlNodeList) then exit(false); IF pFoundXmlNodeList.Count = 0 then exit(false); exit(true); end; procedure FindAttribute(pXMLNode: XmlNode; var pXmlAttribute: XmlAttribute; pAttributeName: Text): Boolean begin exit(pXMLNode.AsXmlElement.Attributes.Get(pAttributeName, pXmlAttribute)); end; procedure GetAttributeValue(pXMLNode: XmlNode; pAttributeName: Text): Text var lXmlAttribute: XmlAttribute; begin If pXMLNode.AsXmlElement.Attributes.Get(pAttributeName, lXmlAttribute) then exit(lXmlAttribute.Value); end; procedure AddDeclaration(var pXMLDocument: XmlDocument; pVersion: Text; pEncoding: Text; pStandalone: Text) var lXmlDeclaration: XmlDeclaration; begin lXmlDeclaration := XmlDeclaration.Create(pVersion, pEncoding, pStandalone); pXMLDocument.SetDeclaration(lXmlDeclaration); end; procedure AddGroupNode(var pXMLNode: XmlNode; pNodeName: Text) var lXMLNewChild: XmlNode; begin AddElement(pXMLNode, pNodeName, '', '', lXMLNewChild); pXMLNode := lXMLNewChild; end; procedure AddNode(var pXMLNode: XmlNode; pNodeName: Text; pNodeText: Text) var lXMLNewChild: XmlNode; begin AddElement(pXMLNode, pNodeName, pNodeText, '', lXMLNewChild); end; procedure AddLastNode(var pXMLNode: XmlNode; pNodeName: Text; pNodeText: Text) var lXMLNewChild: XmlNode; lXMLElement: XmlElement; begin AddElement(pXMLNode, pNodeName, pNodeText, '', lXMLNewChild); if pXMLNode.GetParent(lXMLElement) then pXMLNode := lXMLElement.AsXmlNode; end; procedure AddNamespaces(var pXmlNsMgr: XmlNamespaceManager; pXMLDocument: XmlDocument) var lXmlAttributeCollection: XmlAttributeCollection; lXmlAttribute: XmlAttribute; lXMLElement: XmlElement; begin pXmlNsMgr.NameTable(pXMLDocument.NameTable); pXMLDocument.GetRoot(lXMLElement); lXmlAttributeCollection := lXMLElement.Attributes; IF lXMLElement.NamespaceUri <> '' then pXmlNsMgr.AddNamespace('', lXMLElement.NamespaceUri); Foreach lXmlAttribute in lXmlAttributeCollection do begin if StrPos(lXmlAttribute.Name, 'xmlns:') = 1 then pXmlNsMgr.AddNamespace(DELSTR(lXmlAttribute.Name, 1, 6), lXmlAttribute.Value); end; end; procedure AddNameSpace(var pXmlNsMgr: XmlNamespaceManager; pPrefix: text; pNamespace: text); begin pXmlNsMgr.AddNamespace(pPrefix, pNamespace); end; procedure XMLEscape(pText: Text): Text var lXMLDocument: XmlDocument; lRootXmlNode: XmlNode; lXmlNode: XmlNode; begin lXMLDocument := XmlDocument.Create; AddElement(lRootXmlNode, 'XMLEscape', pText, '', lXmlNode); exit(lXmlNode.AsXmlElement.InnerXml); end; procedure LoadXMLDocumentFromText(pXMLText: Text; var pXMLDocument: XmlDocument) begin IF pXMLText = '' then exit; XmlDocument.ReadFrom(pXMLText, pXMLDocument); end; procedure LoadXMLNodeFromText(pXMLText: Text; var pXMLNode: XmlNode) var lXmlDocument: XmlDocument; begin LoadXMLDocumentFromText(pXMLText, lXmlDocument); pXMLNode := lXmlDocument.AsXmlNode; end; procedure LoadXMLDocumentFromInStream(pInStream: InStream; var pXMLDocument: XmlDocument) begin XmlDocument.ReadFrom(pInStream, pXMLDocument); end; procedure LoadXMLNodeFromInStream(pInStream: InStream; var pXMLNode: XmlNode) var lXmlDocument: XmlDocument; begin LoadXMLDocumentFromInStream(pInStream, lXmlDocument); pXMLNode := lXmlDocument.AsXmlNode; end; procedure RemoveNamespaces(pXmlText: Text): Text var XMLDOMMgt: Codeunit "XML DOM Management"; begin Exit(XMLDOMMgt.RemoveNamespaces(pXmlText)); end; procedure SetUtf8Declaration(var pXMLDocument: XmlDocument); var Declaration : XmlDeclaration; begin Declaration := XmlDeclaration.Create('1.0', 'utf-8', 'yes'); pXMLDocument.SetDeclaration(Declaration); end; }
3 Responses to “AL and SOAP”