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

 

Twain Scanner for Dynamics NAV that works in remote desktop

A few days ago I published objects for NAV 2013 and details on how to use a twain scanner with the hardware hub.

I know that many happy Dynamics NAV users are still using older versions so I wanted to make this solution available for NAV 2009, both in the Role Tailored Client and in the Classic Client.

ClassicHubScanning

If you are running NAV 2009 you should be able to use the attached object to test your scanner.  It should be easy to change the code to work with older versions of Dynamics NAV.

Hub Scanning for NAV 2009

Hardware Hub IIS Service on Objects4NAV.com

Session Event already exists during a full restore

In NAV 2013 distribution Microsoft is not delivering a database backup.  You will need to create the backup file from the SQL demo database.  This is what I did.

I created a new database and started a full restore and got the error.

So I cleared the Session Event table before making a new backup and the problem is solved.

SessionEventDelete