Ping your web service for security

Increasingly we are using Web Services to communicate between NAV companies.  In a production environment these communication is usually secured by the SSL standard. When the communication is sent over the Internet I would recommend that a secure ping be added before every communication to make sure that the correct machines are speaking together.

The payment service module in NAV 2013 R2 has an encryption functionality.  I am using this functionality every time that I store user names and passwords in the database. This encryption functionality is based on the key located in the Online Service add-in folder.

KeyLocation

You can copy the Providerkey.key file from one server to another or use the Payment Service Connection Setup page to upload, download, delete and to create a new key.

EncryptionSetup

Create a function and add to you web service.

EncryptedPingWebService

 

The encrypted string is then decrypted and compared with the original string sent.

EncryptedPing

The EncryptionMgt refers to Codeunit 824.  A possibility to add the secret as a parameter to every web service call and skip the preceding ping function.  The code examples can be downloaded from Objects4NAV.

 

Scanning and attachments for Incoming Documents in Microsoft Dynamics NAV 2013 R2

A new feature in NAV 2013 R2 allows companies to store links to incoming documents and reference them in Purchase Invoices and Journal Lines.  There is a video in the How Do I series on Managing Incoming Documents in Microsoft Dynamics NAV 2013 R2.

I wanted to add a functionality to scan and store the files within NAV.  Using the Hardware Hub and the Hardware Hub Twain Client to easily scan documents and store them in NAV.  The objects needed are attached below.  You will need to put the Hardware Hub Proxy Add-in to the NAV 2013 R2 Server Add-in folder.  There is a possibility of doing this without the add-in by manually creating the soap requests.  I did this in the NAV 2009 R2 version for the Classic Client and if needed I should be able to make available a similar NAV 2013 R2 version.

So, how does it work ?

First step is to download and install the Hardware Hub Twain Client on the computer with a twain compatible scanner and the Hardware Hub Proxy Add-in to the Server Add-in folder..  Double click the icon in the notification area to get the program windows in foreground.  Next step is to install the new and modified objects into your NAV 2013 R2 database.

Then go to Incoming Document in the Windows Client.  After an incoming document entry has been created the New Attachment button will be enabled.NewIncomingDocumentEntry

Click New Attachment button.  The first time you open this page you will need to set up the scanner connection.

ScanningSetup

You need to compare the Hardware Hub Path and the Hardware Hub Scanner GUID to the Twain Client window.  It should be enough to copy the GUID from NAV to the Twain Client.

TwainClient

Minimize the Twain Client and go to the ACTIONS menu to select scanner.  You can test the Hardware Hub connection with the About action.

SelectScanner

These settings will be saved with the page data personalization for the current user.

If you select to “Show Settings” the settings dialog will sometimes appear behind the NAV Windows Client – just to let you know.

You can also browse for an existing file in the File Name drill down arrow and that file will be uploaded into NAV.  When you close this window the URL to the Incoming Document Store will be added to the Incoming Document.  You can add multiple files to a single Incoming Document and the URL will open all of them.

This solution will work on a cloud based NAV and also via remote desktop.  I was able to scan with the Web Client but that functionality is not fully tested.  Your feedback is always appreciated.

Objects: IncomingDocumentStore, 2014-07-29 Update

Hardware Hub IIS Service on Objects4NAV.com

ClosedXML gives me a SmartTag error in NAV 2013 R2

In NAV 2009 R2 I did a solution that used ClosedXML to create Excel Documents.  ClosedXML is built to give an easy access to the office documents and is built on top of the Open XML SDK 2.0 for Microsoft Office.

I upgraded this solution to NAV 2013 without any problems.  Then to NAV 2013 R2 and there is where the problem started.

With NAV 2013 Microsoft changed the way NAV creates Excel documents.  They move away from the old COM model to this Open XML method.  You can see the changes in Table 370 Excel Buffer.

In NAV 2013 Microsoft used version 2.0 of the Open XML SDK.  In NAV 2013 R2 Microsoft is using version 2.5 and that is where my problems started.

When trying to save the Excel document with the SaveAs function I got a SmartTag error.

Could not load type ‘DocumentFormat.OpenXml.Spreadsheet.SmartTags’

I searched and found that the SmartTags are discontinued in version 2.5 but they are still used in ClosedXML.  As usual there is a solution and I found it on the Closed XML forum.

A WORKAROUND TO GET CLOSEDXML working for DocumentFormat.OpenXml SDK 2.5:

  1. Download ClosedXML source code
  2. Open ClosedXML source with Visual Studio
  3. Browse to the References of “ClosedXML” project and remove the reference DocumentFormat.OpenXml
  4. Use Nuget to install the latest Document.OpenXml 2.5 SDK as reference into the “ClosedXML” project
  5. Open the file XLWSContentManager.cs and remove the line 86
  6. Compile the project

For some reason it does not work for that way for “ClosedXML_Net3.5” project.

I opened my NAV 2013 R2 Developement machine with Visual Studio 2012, opened the solution and removed the ClosedXML_Net3.5 project.  Then replaced all references to Document.OpenXML with the pre-installed version 2.5 – yes the version 2.5 is installed with NAV Server.

The new ClosedXML 0.69.1.0 was built and it works with OpenXML 2.5 and I can now do the upgrade from NAV 2013 to NAV 2013 R2.

Here is the ClosedXML version 0.69.1.0 rebuilt for Document.OpenXML 2.5

Could not load type “. error in DotNet object

As often a solution to a strange error is so simple when you know it.  When I copied my add-ins to a  new computer I was not able compile the objects that included DotNet add-ins.

CouldNotLoadType

As this happened when I was installing NAV 2013 R2 for the first time my first thought was that this was something that had changed between versions and I sent a support request to Microsoft.

Mohamad Vajid and Duilio Tacconi took the case and tried to reproduce the problem without success.  Yesterday we took a look at the problem together and the solution came to our attention.

The Add-in was copied from a web site and because of that the file was blocked by the server operating system.

UnblockDLL

Then today as I was creating a new website with files that I downloaded from the web I had the same problem.  That will be the last time.

After you unblock the file, recheck to see if that worked.  If the file is in a protected folder you might need to drag it to your desktop to unblock it and back again.

Thank you Mohamad and Duilio for the assistance.

Hardware Hub Update

I just updated the hardware hub service on Dynamics.  I added functions to send and receive status.  I had problems with the Topaz signature pad as the status updates from the pad where interfering with the commands sent between NAV and the Topaz client.

I now send the updates via the status service and things are working better for me.

A little recap on the Hardware Hub.  This is a web service used as a bridge between NAV and a hardware client running on the host computer.  This moves all communication between the software and the hardware to standard web requests.  That means that as long as the NAV service and the hardware client can both access the Hub service they will always be able to communicate.

To follow the update on the service I also updated the proxy add-in for Dynamcis NAV.

HardwareHubProxy

Hardware Hub IIS Service on Objects4NAV.com

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

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

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.

Scan document with NAV via the Hardware Hub

I have now created a document scanning solution that uses the Hardware Hub.  This means that you can place the scanner on any computer and the NAV Windows Client on any or the same computer.

All you need is the Hardware Hub Twain Client on the computer that is connected to the scanner.  The software is available for download and install in by selecting the link above.

Attached is a Page and a Codeunit for NAV 2013 for you to test the scanning.  Import the fob file into your NAV and run page 50093.  This will require the Hardware Hub Proxy installed on your server or your client add-ins folder.

HubScanning

Use the AssistEdit button to create a new Twain Scanner GUID.  The installed Hardware Hub Twain Client will minimize to you notification area.  Locate and double-click the notification icon to bring up the client settings and copy the GUID from the page to the client.

HubCLient

Then just minimize the client again to the notification area.  Close and reopen page 50093, select your scanner and scan.  The scanned document will be located on the server and you can download and open the document with the assist button.

In the attached ZIP file you will find the file HardwareHubProxy.dll that you will need to put in your server add-ins folder.  If you would like to test this on your local machine just change the RunOnClient property in Codeunit 50093 and put the HardwareHubProxy.dll in your Windows Client add-ins folder.

HubLocalSettings

Here is the required objects.  HubScanning
Updated HardwareHubProxy.dll

Hardware Hub IIS Service on Objects4NAV.com