My Excel Helper to bring opened Excel Documents to front

In one of my projects I use Excel a lot for reporting.  I create the Excel documents with OpenXML and with the help of the Excel Buffer table.  With the click of a button the user creates a beautiful Excel Workbook but when I open the workbook it is displayed in the back of Dynamics NAV client window.

One of the users has been complaining about this and in the process of upgrading from NAV 2009 to NAV 2013 he asked me if this will be fixed.  I of course had to in order to increase the appreciation of the upgrade.

So I looked into this but did not find any .net object to use.  Looked like I had to import user32.dll to do this and that is not possible directly in NAV.  Hence a new class dll was born.

[code language=”csharp”] public static class WindowHelper
{
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);

public static void ActivateAppByProcessName(string processName)
{
Process[] p = Process.GetProcessesByName(processName);
if (p.Length > 0)
ActivateAppByHandle(p[0].MainWindowHandle);
}

public static void ActivateAppByPartialProcessName(string processName)
{
Process[] processes = Process.GetProcesses();
foreach (Process process in processes)
{
if (process.MainWindowTitle.Contains(processName))
{
ActivateAppByHandle(process.MainWindowHandle);
}
}
}

public static void ActivateAppByFileName(string fileName)
{
string[] fileNameParts = fileName.Split(new Char[] { ‘.’ });
string windowTitle = "";
for (int i = 0; i < fileNameParts.Length – 1; i++)
{
if (i == 0)
{
windowTitle += fileNameParts[i];
}
else
{
windowTitle += "." + fileNameParts[i];
}
}
ActivateAppByPartialProcessName(windowTitle);
}

static void ActivateAppByHandle(System.IntPtr WindowHandle)
{
SetForegroundWindow(WindowHandle);
}
}[/code]

In the same solution I had a problem converting the Excel Workbook to a PDF file. So I also added that support to this class.

[code language=”csharp”] public static class ExcelHelper
{
public static void ExportPDF(Microsoft.Office.Interop.Excel.Workbook xlWrkBk, string pdfFileName)
{
xlWrkBk.ExportAsFixedFormat(Microsoft.Office.Interop.Excel.XlFixedFormatType.xlTypePDF, pdfFileName);
}
}[/code]

Now I am able to open Excel in front of the NAV client and also able to convert the Excel Workbook to a PDF file.  In Table 370, Excel Buffer, I add a local variable for this class and add a single line in the code.

[code]The changes I make are below[code] PROCEDURE GiveUserControl@3();
VAR
WindowHelper@50000 : DotNet "’UserHelperClass, Version=1.0.0.0, Culture=neutral, PublicKeyToken=22affbe033b077df’.UserHelperClass.WindowHelper" RUNONCLIENT;
BEGIN
IF NOT ISNULL(XlApp) THEN BEGIN
XlApp.Visible := TRUE;
// Dynamics.is – Bring Excel to front –
WindowHelper.ActivateAppByFileName(XlWrkBk.Name);
// Dynamics.is – Bring Excel to front +
XlApp.UserControl := TRUE;
CLEAR(XlApp);
END;
END;
[/code]

And in Codeunit 424 in the bottom of the ExportData trigger

[code]
xlApp.Visible := TRUE;
//Dynamics.is – Bring Excel to Front –
WindowHelper.ActivateAppByFileName(FileMgt.GetFileName(FileName));
//Dynamics.is – Bring Excel to Front +[/code]

The class and then changed objects are attached below.

UserHelperClass  ExcelBufferUpdate

Use SAVEASPDF to E-Mail Invoices and Credit Memos

When I was running NAV 2009 I used PDFCreator, BioPDF or BullZipPDF printers to create a PDF copy of a report.  In NAV 2013 we have the option to use native SAVEASPDF command that uses built-in methods to create a PDF document on the server.

I used the Job Queue in NAV 2009 to send all my invoices and credit memos via email to my customers.  An updated version for NAV 2013 that uses

[code]
FileName := FileMgt.GetDirectoryName(FileMgt.ServerTempFileName(‘pdf’)) + ‘\’ + STRSUBSTNO(‘%1 %2.pdf’,TABLECAPTION,"No.");
IF EXISTS(FileName) THEN
ERASE(FileName);
SalesInvHeader := "Sales Invoice Header";
SalesInvHeader.SETRECFILTER;
CLEAR(Invoice);
Invoice.SETTABLEVIEW(SalesInvHeader);
Invoice.USEREQUESTPAGE(FALSE);
IF Invoice.SAVEASPDF(FileName) THEN BEGIN[/code]

is attached below.

JobQueueEMailer2013

Images on the NAV 2013 Role Center Ribbon

As a NAV 2013 user I have customized my role center.  That includes putting on my Action Ribbon my most used actions.

NAV2013Ribbon

Then I searched for a way to update the images for my newly added actions but found none.  I asked on Mibuso and got confirmed from Mark Brummel that this option is not available.

From the Departments menu I right-click my most used items and select to “Add to Actions on Role Center Ribbon”.  On the Ribbon I want to be able to customize the images to my customized actions.  I suggested this on Microsoft Connect and if you agree please vote for the suggestion.

Copy data between companies and/or databases

I am upgrading a solution from NAV 2009 to NAV 2013.  The solution was running in the Role Tailored Client only so the upgrade process was fast and easy.

I wanted to move this customized solution to another database and remove all other customizations from the database.  On the other hand I am upgrading everything in the old database except this customized solution with the standard methods.

After I had the solution upgraded and the new database ready I started to think about how I would get the data from the old database.  It is a significant amount of data so I wanted to use the native sql commands to do this.  Since I had around one hundred tables I felt it would not be practical to copy manually nor to create the copy script manually.

So, I created a batch report.  The batch creates an INSERT INTO and SELECT FROM query that is very fast.  It requires the tables to be identical in both databases.

CreateCopyScript

This batch will create a copy script that runs fast…

Few things I had to check.  When copying Blob fields from NAV 2009 to NAV 2013 I needed to change the Compressed property to No.  I also needed to check the fields to make sure that fields with AutoIncrement property set to Yes would not be included in the script.

I started with a NAV 2009 batch that is basically raw.  I made the NAV 2013 batch smarter with direct connection to the sql server.

Here are both NAV Copy for 2009 and NAV Copy 2013 (2).

Reading the NAV 2013 Service Configuration

In a previous blog I showed how to get the database name and the database server name for NAV 2009.  I used Automation object to read the service configuration file and pull from there the required information.

This method is obsolete for NAV 2013 but here is the required code to pick similar and more information from the service configuration file.

[code]OBJECT Codeunit 50000 Read Service Config
{
OBJECT-PROPERTIES
{
Date=18.06.13;
Time=22:51:31;
Modified=Yes;
Version List=Dynamics.is;
}
PROPERTIES
{
SingleInstance=Yes;
OnRun=BEGIN
END;

}
CODE
{

PROCEDURE FindMiddleTierServicePath@1000000002() ServicePath : Text[1024];
VAR
ActiveSession@1000000002 : Record 2000000110;
ServerFile@1000000001 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.IO.File";
XMLDoc@1000000000 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlDocument";
XMLNode@1000000003 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlNode";
BEGIN
ActiveSession.SETRANGE("Session ID",SESSIONID);
ActiveSession.FINDFIRST;

XMLDoc := XMLDoc.XmlDocument;
IF ServerFile.Exists(APPLICATIONPATH + ‘Instances\’ + ActiveSession."Server Instance Name" + ‘\CustomSettings.config’) THEN
XMLDoc.Load(APPLICATIONPATH + ‘Instances\’ + ActiveSession."Server Instance Name" + ‘\CustomSettings.config’)
ELSE
XMLDoc.Load(APPLICATIONPATH + ‘CustomSettings.config’);

ServicePath := ‘DynamicsNAV://’ + ActiveSession."Server Computer Name" + ‘:’;

XMLNode := XMLDoc.SelectSingleNode(‘//appSettings/add[@key=”ClientServicesPort”]’);
ServicePath := ServicePath + XMLNode.Attributes.Item(1).InnerText + ‘/’ + ActiveSession."Server Instance Name";
CLEAR(XMLDoc);
END;

PROCEDURE FindSOAPWebServicePath@10010403() ServicePath : Text[1024];
VAR
ActiveSession@1000000002 : Record 2000000110;
ServerFile@1000000001 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.IO.File";
XMLDoc@1000000000 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlDocument";
XMLNode@1000000003 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlNode";
httpUtility@1000000004 : DotNet "’System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’.System.Web.HttpUtility";
BEGIN
ActiveSession.SETRANGE("Session ID",SESSIONID);
ActiveSession.FINDFIRST;

httpUtility := httpUtility.HttpUtility;
XMLDoc := XMLDoc.XmlDocument;
IF ServerFile.Exists(APPLICATIONPATH + ‘Instances\’ + ActiveSession."Server Instance Name" + ‘\CustomSettings.config’) THEN
XMLDoc.Load(APPLICATIONPATH + ‘Instances\’ + ActiveSession."Server Instance Name" + ‘\CustomSettings.config’)
ELSE
XMLDoc.Load(APPLICATIONPATH + ‘CustomSettings.config’);

XMLNode := XMLDoc.SelectSingleNode(‘//appSettings/add[@key=”SOAPServicesSSLEnabled”]’);
IF UPPERCASE(XMLNode.Attributes.Item(1).InnerText) = ‘FALSE’ THEN
ServicePath := ‘http://’
ELSE
ServicePath := ‘https://’;

ServicePath := ServicePath + ActiveSession."Server Computer Name" + ‘:’;

XMLNode := XMLDoc.SelectSingleNode(‘//appSettings/add[@key=”SOAPServicesPort”]’);
ServicePath := ServicePath + XMLNode.Attributes.Item(1).InnerText + ‘/’ + ActiveSession."Server Instance Name" + ‘/WS/’;
ServicePath := ServicePath + httpUtility.UrlPathEncode(COMPANYNAME) + ‘/Services’;

CLEAR(XMLDoc);
END;

PROCEDURE FindODataWebServicePath@10010405() ServicePath : Text[1024];
VAR
ActiveSession@1000000002 : Record 2000000110;
ServerFile@1000000001 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.IO.File";
XMLDoc@1000000000 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlDocument";
XMLNode@1000000003 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlNode";
BEGIN
ActiveSession.SETRANGE("Session ID",SESSIONID);
ActiveSession.FINDFIRST;

XMLDoc := XMLDoc.XmlDocument;
IF ServerFile.Exists(APPLICATIONPATH + ‘Instances\’ + ActiveSession."Server Instance Name" + ‘\CustomSettings.config’) THEN
XMLDoc.Load(APPLICATIONPATH + ‘Instances\’ + ActiveSession."Server Instance Name" + ‘\CustomSettings.config’)
ELSE
XMLDoc.Load(APPLICATIONPATH + ‘CustomSettings.config’);

ServicePath := ‘http://’ + ActiveSession."Server Computer Name" + ‘:’;

XMLNode := XMLDoc.SelectSingleNode(‘//appSettings/add[@key=”ODataServicesPort”]’);
ServicePath := ServicePath + XMLNode.Attributes.Item(1).InnerText + ‘/’ + ActiveSession."Server Instance Name" + ‘/OData/’;

CLEAR(XMLDoc);
END;

PROCEDURE FindDatabaseServerName@1000000000() DatabaseServerName : Text[1024];
VAR
ActiveSession@1000000003 : Record 2000000110;
ServerFile@1000000002 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.IO.File";
XMLDoc@1000000001 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlDocument";
XMLNode@1000000000 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlNode";
DatabaseServer@1000000004 : Text[1024];
DatabaseInstance@1000000005 : Text[1023];
BEGIN
ActiveSession.SETRANGE("Session ID",SESSIONID);
ActiveSession.FINDFIRST;

XMLDoc := XMLDoc.XmlDocument;
IF ServerFile.Exists(APPLICATIONPATH + ‘Instances\’ + ActiveSession."Server Instance Name" + ‘\CustomSettings.config’) THEN
XMLDoc.Load(APPLICATIONPATH + ‘Instances\’ + ActiveSession."Server Instance Name" + ‘\CustomSettings.config’)
ELSE
XMLDoc.Load(APPLICATIONPATH + ‘CustomSettings.config’);

XMLNode := XMLDoc.SelectSingleNode(‘//appSettings/add[@key=”DatabaseServer”]’);
DatabaseServer := XMLNode.Attributes.Item(1).InnerText;

XMLNode := XMLDoc.SelectSingleNode(‘//appSettings/add[@key=”DatabaseInstance”]’);
DatabaseInstance := XMLNode.Attributes.Item(1).InnerText;

CLEAR(XMLDoc);

IF DatabaseInstance = ” THEN
DatabaseServerName := DatabaseServer
ELSE
DatabaseServerName := DatabaseServer + ‘\’ + DatabaseInstance;
END;

BEGIN
END.
}
}

[/code]

The four functions here will give you the path to start the Windows Client, the path to the Soap Web Service, the path to OData Web Service and finally the database server name including instance name if used.

The Codeunit is available here: ReadServiceConfig

I want two new fields added to NAV 2013

In the tables 92, Customer Posting Group and 93, Vendor Posting Group I would like Microsoft to add a Description field.  This is a default field in almost all setup tables in the solution.  Don’t know why it is not in those tables.

So, I suggested this on Microsoft Connect and you can vote here.

Web Client not in the installation language

I have installed the Web Client on several computers.  It was not until yesterday that I finally got to check why the client was not displaying my Icelandic language.  I always got English even if the Icelandic language module was installed.

As I looked at the Web.Config file (in the folder C:\Program Files\Microsoft Dynamics NAV\70\Web Client) I saw that the Icelandic installer change the Language and RegionFormat to France !

[code] <add key="TimeZone" value="UTC" />
<add key="Language" value="fr-FR" />
<!– The locale for formatting dates, times, numbers and currency. This is required. –>
<add key="RegionFormat" value="fr-FR" />[/code]

Now, change the fr-FR to is-IS and save (requires elevated access) and now I have Icelandic Web Client. I have reported this to Microsoft and they will most likely fix this soon. If I will be notified I will also let you know.

WebClientIcelandic

Text files, reading, writing, converting and different code pages

Microsoft Dynamics NAV is still using the old DOS code page for files.  If you create a file with the file variable and write text to that file you will get a DOS file.  The same thing happens when writing to a BLOB and exporting to a file.  The Code example below handles the DOS code page.

[code]OBJECT Codeunit 50000 Read and Write DOS File
{
OBJECT-PROPERTIES
{
Date=30.05.13;
Time=09:16:44;
Modified=Yes;
Version List=Dynamics.is;
}
PROPERTIES
{
OnRun=VAR
LineRead@10000000 : Text[250];
CrLf@10000001 : Text[2];
BEGIN
CrLf[1] := 13;
CrLf[2] := 10;
DOSFileName := FileMgt.ServerTempFileName(‘txt’);
DOSFile.CREATE(DOSFileName);
DOSFile.CREATEOUTSTREAM(OutStr);
StandardText.FINDSET;
REPEAT
OutStr.WRITETEXT(STRSUBSTNO(‘%1,%2’,StandardText.Code,StandardText.Description) + CrLf);
UNTIL StandardText.NEXT = 0;
DOSFile.CLOSE;

DOSFile.OPEN(DOSFileName);
DOSFile.CREATEINSTREAM(InStr);
WHILE NOT InStr.EOS DO BEGIN
InStr.READTEXT(LineRead,MAXSTRLEN(LineRead));
TempStandardText.Code := SELECTSTR(1,LineRead);
TempStandardText.Description := SELECTSTR(2,LineRead);
TempStandardText.INSERT;
END;
DOSFile.CLOSE;

MESSAGE(Text001,DOSFileName);
PAGE.RUNMODAL(PAGE::"Standard Text Codes",TempStandardText);
END;

}
CODE
{
VAR
StandardText@10000007 : Record 7;
TempStandardText@10000006 : TEMPORARY Record 7;
FileMgt@10000003 : Codeunit 419;
DOSFile@10000000 : File;
DOSFileName@10000004 : Text[250];
InStr@10000001 : InStream;
OutStr@10000002 : OutStream;
Text001@10000005 : TextConst ‘ENU=Server File Name : %1;ISL=Skr�arnafn � �j�ni : %1’;

BEGIN
END.
}
}[/code]

Using DotNet for the same job as the below example shows, will create a file with the Windows code page.

[code]OBJECT Codeunit 50001 Read and Write Windows File
{
OBJECT-PROPERTIES
{
Date=30.05.13;
Time=09:26:03;
Modified=Yes;
Version List=Dynamics.is;
}
PROPERTIES
{
OnRun=VAR
LineRead@10000000 : Text[250];
CrLf@10000001 : Text[2];
Loop@10000002 : Integer;
BEGIN
CrLf[1] := 13;
CrLf[2] := 10;
ISOFileName := FileMgt.ServerTempFileName(‘txt’);
StandardText.FINDSET;
REPEAT
dotNetFile.AppendAllText(ISOFileName,STRSUBSTNO(‘%1,%2′,StandardText.Code,StandardText.Description) + CrLf);
UNTIL StandardText.NEXT = 0;

dotNetArray := dotNetFile.ReadAllLines(ISOFileName);
FOR Loop := 0 TO (dotNetArray.Length – 1) DO BEGIN
LineRead := dotNetArray.GetValue(Loop);
TempStandardText.Code := SELECTSTR(1,LineRead);
TempStandardText.Description := SELECTSTR(2,LineRead);
TempStandardText.INSERT;
END;

MESSAGE(Text001,ISOFileName);
PAGE.RUNMODAL(PAGE::"Standard Text Codes",TempStandardText);
END;

}
CODE
{
VAR
dotNetFile@10000011 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.IO.File";
dotNetArray@10000010 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’.System.Array";
StandardText@10000007 : Record 7;
TempStandardText@10000006 : TEMPORARY Record 7;
FileMgt@10000003 : Codeunit 419;
ISOFileName@10000004 : Text[250];
Text001@10000005 : TextConst ‘ENU=Server File Name : %1;ISL=Skr�arnafn � �j�ni : %1’;

BEGIN
END.
}
}
[/code]

And to write and read UTF-8 encoded file

[code]OBJECT Codeunit 50002 Read and Write UTF8 File
{
OBJECT-PROPERTIES
{
Date=30.05.13;
Time=09:26:51;
Modified=Yes;
Version List=Dynamics.is;
}
PROPERTIES
{
OnRun=VAR
LineRead@10000000 : Text[250];
CrLf@10000001 : Text[2];
Loop@10000002 : Integer;
BEGIN
CrLf[1] := 13;
CrLf[2] := 10;
ISOFileName := FileMgt.ServerTempFileName(‘txt’);
StandardText.FINDSET;
REPEAT
dotNetFile.AppendAllText(ISOFileName,STRSUBSTNO(‘%1,%2’,StandardText.Code,StandardText.Description) + CrLf,Encoding.GetEncoding(‘utf-8’));
UNTIL StandardText.NEXT = 0;

dotNetArray := dotNetFile.ReadAllLines(ISOFileName,Encoding.GetEncoding(‘utf-8′));
FOR Loop := 0 TO (dotNetArray.Length – 1) DO BEGIN
LineRead := dotNetArray.GetValue(Loop);
TempStandardText.Code := SELECTSTR(1,LineRead);
TempStandardText.Description := SELECTSTR(2,LineRead);
TempStandardText.INSERT;
END;

MESSAGE(Text001,ISOFileName);
PAGE.RUNMODAL(PAGE::"Standard Text Codes",TempStandardText);
END;

}
CODE
{
VAR
dotNetFile@10000011 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.IO.File";
dotNetArray@10000010 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Array";
Encoding@10000000 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’.System.Text.Encoding";
StandardText@10000007 : Record 7;
TempStandardText@10000006 : TEMPORARY Record 7;
FileMgt@10000003 : Codeunit 419;
ISOFileName@10000004 : Text[250];
Text001@10000005 : TextConst ‘ENU=Server File Name : %1;ISL=Skr�arnafn � �j�ni : %1’;

BEGIN
END.
}
}
[/code]

This also gives us an easy way to convert files from one code page to another. For example from the DOS format to the Windows format.

[code]
ServerISOFileName := FileMgt.ServerTempFileName(‘xml’);
dotNetFile.WriteAllText(
ServerISOFileName,
dotNetFile.ReadAllText(ServerDOSFileName,Encoding.GetEncoding(‘ibm850’)),
Encoding.GetEncoding(‘iso-8859-1’));[/code]

Also if you use the UTF-8 example and replace GetEncoding(‘utf-8’) with GetEncoding(‘ibm850’) you will get a DOS formatted file.  Microsoft offers a list of all supported encoding methods here.  The beauty with the DotNet methods is the possibility to use RunOnClient property to read and write files from the client computer.

Minimize C/AL Code for Online Communication

These days I keep busy upgrading Advania solutions to Dynamics NAV 2013.  All the older solutions that have communicated with web services have DOM objects and manually created XML files.  We had to add and remove namespace to be able to use XML ports but in the end this worked flawlessly.

In NAV 2013 it is possible to use DOM objects on the client-side but my mission is to execute as much as possible on the server-side and use dotnet interoperability in all cases.  That left me with two choices; rewrite all the DOM C/AL Code and use dotnet or build a class library and minimize the code as much as possible.

I tried both and for me the choice is simple.  I will go with the class library method.

If I have the WSDL (Web Services Description Language) as a file or supplied by the web service then I will get both the commands and the data types needed for the communication.  The C/AL Code needed can be as little as four lines.

[code]
Greidslur := Greidslur.GreidslurWS; // Construct the SoapHTTPClientProtocol
Greidslur.Url := XMLURLPaymentsUnsigned; // Set the URL to the service
SecurityHelper.SetUserName(Greidslur,WebServiceUserID,WebServicePassword); // Add authentication if needed
DoesExist := Greidslur.ErReikningurTil(BankNo,LedgerNo,AccountNo,OwnerID); // Carry out the communication[/code]

The identical C/AL Code required to do the same query with DOM is a lot longer

[code]DocumentReady := CREATE(DOMDocument);
IF NOT DocumentReady THEN
ERROR(Text003);

IXMLDOMProcessingInstruction := DOMDocument.createProcessingInstruction(‘xml’,’version="1.0" encoding="utf-8"’);
DOMDocument.appendChild(IXMLDOMProcessingInstruction);
IXMLDOMElement := DOMDocument.createElement(‘soap:Envelope’);
IXMLDOMElement.setAttribute(‘xmlns:soap’,’http://schemas.xmlsoap.org/soap/envelope/’);
IXMLDOMElement.setAttribute(‘xmlns:xsi’,’http://www.w3.org/2001/XMLSchema-instance’);
IXMLDOMElement.setAttribute(‘xmlns:xsd’,’http://www.w3.org/2001/XMLSchema’);
CreateHeader(IXMLDOMElement2); // Creates the authentication
IXMLDOMElement3 := DOMDocument.createElement(‘soap:Body’);
IXMLDOMElement4 := DOMDocument.createElement(‘ErReikningurTil’);
IXMLDOMElement4.setAttribute(‘xmlns’,’http://ws.isb.is’);
IXMLDOMElement5 := DOMDocument.createElement(‘banki’);
IXMLDOMElement5.nodeTypedValue(BankNo);
IXMLDOMElement4.appendChild(IXMLDOMElement5);
IXMLDOMElement5 := DOMDocument.createElement(‘hofudbok’);
IXMLDOMElement5.nodeTypedValue(LedgerNo);
IXMLDOMElement4.appendChild(IXMLDOMElement5);
IXMLDOMElement5 := DOMDocument.createElement(‘reikningsnumer’);
IXMLDOMElement5.nodeTypedValue(AccountNo);
IXMLDOMElement4.appendChild(IXMLDOMElement5);
IXMLDOMElement5 := DOMDocument.createElement(‘kennitala’);
IXMLDOMElement5.nodeTypedValue(OwnerID);
IXMLDOMElement4.appendChild(IXMLDOMElement5);
IXMLDOMElement3.appendChild(IXMLDOMElement4);
IXMLDOMElement.appendChild(IXMLDOMElement2);
IXMLDOMElement.appendChild(IXMLDOMElement3);
DOMDocument.appendChild(IXMLDOMElement);

XMLHttp.open( ‘POST’, XMLURLPaymentsUnsigned, FALSE);
XMLHttp.setRequestHeader(‘soapAction’,’http://ws.isb.is/ErReikningurTil’);
XMLHttp.send(DOMDocument);
DOMResponseDocument := XMLHttp.responseXML;

IXMLDOMNode := DOMResponseDocument.selectSingleNode(‘/soap:Envelope/soap:Body’);
IXMLDOMNodeList2 := IXMLDOMNode.childNodes;
FOR j := 1 TO IXMLDOMNodeList2.length DO BEGIN
IXMLDOMNode2 := IXMLDOMNodeList2.nextNode;
CASE IXMLDOMNode2.nodeName OF
‘soap:Fault’:
BEGIN
IXMLDOMNodeList3 := IXMLDOMNode2.childNodes;
FOR k := 1 TO IXMLDOMNodeList3.length DO BEGIN
IXMLDOMNode4 := IXMLDOMNodeList3.nextNode;
CASE IXMLDOMNode4.nodeName OF
‘faultcode’:
BEGIN
END;
‘faultstring’:
BEGIN
ERROR(DecodeText(IXMLDOMNode4.text));
END;
‘faultfactor’:
BEGIN
END;
END;
END;
END;
‘ErReikningurTilResponse’:
BEGIN
IXMLDOMNodeList3 := IXMLDOMNode2.childNodes;
IXMLDOMNode3 := IXMLDOMNodeList3.nextNode;
CASE IXMLDOMNode3.nodeName OF
‘ErReikningurTilResult’:
BEGIN
IF UPPERCASE(IXMLDOMNode3.text) = ‘TRUE’ THEN
DoesExist := TRUE;
END;
END;
END;
END;
END;

CLEAR(DOMDocument);
CLEAR(DOMResponseDocument);[/code]

This example should show you not only that the class library method is simpler but also the potential error in creating the XML is no longer available.

But, there is a but.  There are web services that do not have WSDL.  Just a simple ‘POST’ or ‘GET’ services that requires incoming XML and respond with a XML.  For all the XML files needed there should be a XML Schema Definition or what is normally knows as a XSD file.  If that is the case then there is also a dotnet solution for that.

The first step is to collect all the XSD files needed into a single folder on your local computer and start the Visual Studio Command Prompt.

VisualStudioCommandPrompt

Go to the folder with the XSD files.

VisualStudioCommandPromptStarted

Then you use the command “xsd CollectorEntity.xsd /classes” and repeat for all xsd files.  You should now have C# class file for all XSD files.

ClassesCreated

Next step is to build a class library in the same way that I showed before and add the class files to the solution.

VisualStudioAddFiles

Then build the solution and copy the DLL file to your server Add-ins folder and to your developement environment Add-ins folder and you are good to go.  After adding the new class as a dotnet variable to C/AL I am able to handle the data types from the XSD file the same way as if they were created by a WSDL.

[code]Login := Login.Login;
Login.user := WebServiceUserID;
Login.password := WebServicePassword;
Login.version := 1.1;

Login_answer := Login_answer.Login_answer;
Log."Outgoing Message".CREATEOUTSTREAM(OutStr);
PrepareSend(Login,OutStr);
Log.MODIFY;
COMMIT;

Log."Incoming Message".CREATEOUTSTREAM(OutStr);
Send(OutStr);
Log.MODIFY;
COMMIT;

IF Receive(Login_answer,Login_answer.GetType) THEN
SessionID := Login_answer.sessionid;[/code]

The functions, PrepareSend, Send and Receive will work for all data types

[code]OBJECT Codeunit 50000 Dotnet Send and Receive
{
OBJECT-PROPERTIES
{
Date=09.05.13;
Time=12:06:27;
Modified=Yes;
Version List=Dynamics.is;
}
PROPERTIES
{
OnRun=BEGIN
END;

}
CODE
{
VAR
HttpWebRequest@10010413 : DotNet "’System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Net.HttpWebRequest";
XMLResponseDoc@10010414 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlDocument";
ServiceURL@10000003 : Text[1024];
GotSetup@10000004 : Boolean;
Text003@10000005 : TextConst ‘ENU=Error: %1\%2;ISL=St”Ðuvilla %1, %2\\%3\%4′;

PROCEDURE GetSetup@3(NewServiceURL@10000000 : Text[1024]);
BEGIN
IF NOT GotSetup THEN BEGIN
ServiceURL := NewServiceURL;
END;
END;

PROCEDURE PrepareSend@10010407(VAR OutObj@1000000000 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Object";VAR RequestStream@10010401 : OutStream) : Boolean;
VAR
MemoryStream@1000000006 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.IO.MemoryStream";
XmlTextWriter@1000000007 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlTextWriter";
XmlSerializer@1000000008 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.Serialization.XmlSerializer";
XMLRequestDoc@1000000013 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlDocument";
Encoding@1000000010 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’.System.Text.Encoding";
InStr@10010400 : InStream;
BEGIN
Encoding := Encoding.UTF8;
MemoryStream := MemoryStream.MemoryStream;
XmlTextWriter := XmlTextWriter.XmlTextWriter(MemoryStream,Encoding);
XmlSerializer := XmlSerializer.XmlSerializer(OutObj.GetType);
XmlSerializer.Serialize(XmlTextWriter,OutObj);

XMLRequestDoc := XMLRequestDoc.XmlDocument;
XMLRequestDoc.PreserveWhitespace := TRUE;
MemoryStream.Position := 0;
XMLRequestDoc.Load(MemoryStream);
MemoryStream.Close;
XMLRequestDoc.Save(RequestStream);

HttpWebRequest := HttpWebRequest.Create(ServiceURL); // Live Server
HttpWebRequest.Method := ‘POST’;
HttpWebRequest.ContentType := ‘text/xml; charset=utf-8’;
HttpWebRequest.Accept := ‘text/xml’;
HttpWebRequest.UserAgent := ‘XMLClient 1.0′;
MemoryStream := HttpWebRequest.GetRequestStream;
XMLRequestDoc.Save(MemoryStream);
MemoryStream.Flush;
MemoryStream.Close;
END;

PROCEDURE Send@10010405(VAR ResponseStream@10010400 : OutStream) : Boolean;
VAR
HttpWebException@10010401 : DotNet "’System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Net.WebException";
HttpWebResponse@10010402 : DotNet "’System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Net.HttpWebResponse";
HttpStatusCode@1000000012 : DotNet "’System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Net.HttpStatusCode";
MemoryStream@1000000006 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.IO.MemoryStream";
XMLResponseDoc@1000000009 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlDocument";
InStr@10010403 : InStream;
BEGIN
HttpWebResponse := HttpWebRequest.GetResponse;
IF HttpWebResponse.StatusCode.ToString <> HttpStatusCode.OK.ToString THEN
ERROR(Text003,HttpWebResponse.StatusCode.ToString,HttpWebResponse.StatusDescription);

MemoryStream := HttpWebResponse.GetResponseStream;
XMLResponseDoc := XMLResponseDoc.XmlDocument;
XMLResponseDoc.Load(MemoryStream);
MemoryStream.Flush;
MemoryStream.Close;
XMLResponseDoc.Save(ResponseStream);
END;

PROCEDURE Receive@10010406(VAR InObj@1000000001 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Object";typeResponse@1000000002 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Type") : Boolean;
VAR
XmlSerializer@1000000008 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.Serialization.XmlSerializer";
XmlNodeReader@1000000015 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlNodeReader";
BEGIN
XmlNodeReader := XmlNodeReader.XmlNodeReader(XMLResponseDoc.DocumentElement);
XmlSerializer := XmlSerializer.XmlSerializer(typeResponse);
InObj := XmlSerializer.Deserialize(XmlNodeReader);
EXIT(NOT ISNULL(InObj));
END;

EVENT XMLResponseDoc@10010414::NodeInserting@93(sender@10010401 : Variant;e@10010400 : DotNet "’System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlNodeChangedEventArgs");
BEGIN
END;

EVENT XMLResponseDoc@10010414::NodeInserted@94(sender@10010401 : Variant;e@10010400 : DotNet "’System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlNodeChangedEventArgs");
BEGIN
END;

EVENT XMLResponseDoc@10010414::NodeRemoving@95(sender@10010401 : Variant;e@10010400 : DotNet "’System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlNodeChangedEventArgs");
BEGIN
END;

EVENT XMLResponseDoc@10010414::NodeRemoved@96(sender@10010401 : Variant;e@10010400 : DotNet "’System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlNodeChangedEventArgs");
BEGIN
END;

EVENT XMLResponseDoc@10010414::NodeChanging@97(sender@10010401 : Variant;e@10010400 : DotNet "’System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.XmlNodeChangedEventArgs");
BEGIN
END;

EVENT XMLResponseDoc@10010414::NodeChanged@98(sender@10010401 : Variant;e@10010400 : DotNet "’System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’.System.Xml.XmlNodeChangedEventArgs");
BEGIN
END;

BEGIN
END.
}
}

[/code]

If this i still causing you headaches just contact me and we will figure something out.

Old format or invalid type library Excel error in Rapid Start

As a part of the new localized NAV 2013 we need to configure the Rapid Start package to be used with new installations.  As we started to work with the packages trying to export to Excel we got an error.

ExcelLocalizationError

The cause is known by Microsoft.

You receive this error calling an Excel method when the   following conditions are true:   

  • The method requires an LCID (locale identifier).
  • You run an English version of Excel. However, the regional settings for the computer are configured for a non-English language.

If the client computer runs the English version of Excel and the locale for the current user is configured for a language other   than English, Excel will try to locate the language pack for the configured   language. If the language pack is not found, the error is reported.

We saw the same error in NAV 2009 R2 Role Tailored Client.  The solution is simple, download and install a language pack for Microsoft Office that matches your regional settings.  You should find the language pack on the Office Language Interface Pack (LIP) downloads page.

Link for Office 2016