Testing your Dynamics NAV Web Service

I am building a web service for one of my clients and another company is using this web service for an aspx web site.  I realized that I needed to test my web service before I can deliver it to that company.  So, I created a test codeunit for the job.

First I downloaded the universal XML import/export tool from Mibuso.  Then I added a function to the table 60000 XML Buffer that is in the above tool.
[code htmlscript=”false”]Read(VAR DOMDoc : Automation "’Microsoft XML, v6.0′.DOMDocument")
DELETEALL;
DOMNode := DOMDoc.documentElement;
Import2(DOMNode,1);
IF FINDFIRST THEN;[/code]
Next I create a read function in my test codeunit for every function in the web service.  Here is an example.
[code htmlscript=”false”]GetFarmerTankEntryAverageYW(FarmerID : Integer;MinYear : Integer;MaxYear : Integer;MinWeekNo : Integer;MaxWeekNo : Integer)
GetSetup;
CREATE(XMLDoc,TRUE,FALSE);

XMLProsInstr := XMLDoc.createProcessingInstruction(‘xml’,’version="1.0" encoding="utf-8"’);
XMLDoc.appendChild(XMLProsInstr);

CreateEnvelope(XMLElement1);
XMLElement2 := XMLDoc.createElement(‘soap:Body’);
XMLElement3 := XMLDoc.createElement(‘GetFarmerTankEntryAverageYW’);
XMLElement3.setAttribute(‘xmlns’,’urn:microsoft-dynamics-schemas/codeunit/RMWeb’);
CreateElement(XMLElement3, ‘farmerID’, FORMAT(FarmerID,0,9), ”, ”);
CreateElement(XMLElement3, ‘minYear’, FORMAT(MinYear,0,9), ”, ”);
CreateElement(XMLElement3, ‘maxYear’, FORMAT(MaxYear,0,9), ”, ”);
CreateElement(XMLElement3, ‘minWeekNo’, FORMAT(MinWeekNo,0,9), ”, ”);
CreateElement(XMLElement3, ‘maxWeekNo’, FORMAT(MaxWeekNo,0,9), ”, ”);
CreateElement(XMLElement3, ‘tankEntryXML’, ”, ”, ”);
XMLElement2.appendChild(XMLElement3);
XMLElement1.appendChild(XMLElement2);
XMLDoc.appendChild(XMLElement1);

WinHTTP.open(‘POST’,ServiceURL,FALSE,UserName,Password);
WinHTTP.setRequestHeader(‘Content-Type’,’text/xml; charset=utf-8′);
WinHTTP.setRequestHeader(‘SOAPAction’,’GetFarmerTankEntryAverageYW’);
WinHTTP.send(XMLDoc);

IF WinHTTP.status <> 200 THEN
ERROR(Text003,WinHTTP.status,WinHTTP.statusText);

XMLResponseDoc.load(WinHTTP.responseXML);
DisplayDocument(XMLResponseDoc);[/code]
This will use the XML Buffer to read the response document and display the result.  The Text Constant Text003 contains
[code htmlscript=”false”]ENU=Status error %1 %2;ISL=Stöðuvilla %1 %2[/code]
and the four functions used here contain
[code htmlscript=”false”]DisplayDocument(VAR XMLDoc : Automation "’Microsoft XML, v6.0′.DOMDocument")
XMLBuffer.Read(XMLDoc);
COMMIT;
FORM.RUNMODAL(FORM::"XML Buffer");

CreateEnvelope(VAR InElement : Automation "’Microsoft XML, v6.0′.IXMLDOMElement")
InElement := XMLRequestDoc.createElement(‘soap:Envelope’);
InElement.setAttribute(‘xmlns:soap’,’http://schemas.xmlsoap.org/soap/envelope/’);
InElement.setAttribute(‘xmlns:xsi’,’http://www.w3.org/2001/XMLSchema-instance’);
InElement.setAttribute(‘xmlns:xsd’,’http://www.w3.org/2001/XMLSchema’);

CreateElement(VAR InElement : Automation "’Microsoft XML, v6.0′.IXMLDOMElement";InNodeName : Text[50];InNodeValue : Text[250];InAttribu
TempElement := XMLRequestDoc.createElement(InNodeName);
TempElement.nodeTypedValue(InNodeValue);
IF InAttributeName <> ” THEN
TempElement.setAttribute(InAttributeName,InAttributeValue);
InElement.appendChild(TempElement);

GetSetup()
ServiceURL := ‘http://gunnar.dynamics.is:7047/DynamicsNAV/WS/CRONUS/Codeunit/WebService’;
UserName := ‘<Domain\User>’;
Password := ‘<Password>’;
IF ISCLEAR(WinHTTP) THEN
CREATE(WinHTTP,TRUE,FALSE);
IF ISCLEAR(XMLResponseDoc) THEN
CREATE(XMLResponseDoc,TRUE,FALSE);[/code]

VB.NET NAV Application Server

Most of my clients require a running NAV Application Server.  The NAS that is included in NAV 2009 R2 requires a license that is included in most licenses today.  However, there are cases where more than one NAS is needed.  That requires additional NAS licenses.  Where the customer is running NAV 2009 R2 middle tier service this changes.  By running a VB.NET NAV Application Server it is possible to setup multiple services on a single CAL license.  The CAL license is not as expensive as the NAS license.  Here is the solution that I offer.

First, I create a codeunit in NAV

[code htmlscript=”false”]ExecuteCodeunit(CodeunitID : Integer;Log : Boolean) Success : Boolean

IF Log THEN LogEntryNo := InsertLogEntry(5,CodeunitID);
Success := CODEUNIT.RUN(CodeunitID);
IF Log THEN
UpdateLogEntry(LogEntryNo,Success)
ELSE IF NOT Success THEN BEGIN
LogEntryNo := InsertLogEntry(5,CodeunitID);
UpdateLogEntry(LogEntryNo,Success)
END;

InsertLogEntry(ObjectType : ‘,,,Report,,Codeunit’;ObjectNo : Integer) : Integer
WITH JobQueueLogEntry DO BEGIN
INIT;
ID := CREATEGUID;
“User ID” := USERID;
“Start Date/Time” := CURRENTDATETIME;
“Object Type to Run” := ObjectType;
“Object ID to Run” := ObjectNo;
INSERT(TRUE);
COMMIT;
EXIT(“Entry No.”);
END;

UpdateLogEntry(LogEntryNo : Integer;WasSuccess : Boolean)
WITH JobQueueLogEntry DO BEGIN
GET(LogEntryNo);
“End Date/Time” := CURRENTDATETIME;
IF WasSuccess THEN
Status := Status::Success
ELSE BEGIN
Status := Status::Error;
SetErrorMessage(COPYSTR(GETLASTERRORTEXT,1,1000));
END;
MODIFY;
COMMIT;
END;[/code]

This codeunit uses the Job Queue Log to log the execution.  Next step is to publish this codeunit as a web service in table no. 2000000076.  Default web service name is NAVAppServer.

On the server you install the following files (in my case to C:\Program Files (x86)\Dynamics.is\VB.NET NAV Application Server)


Next step is to edit the VB.NET NAV Application Server.exe.config file and customize the values.

 <applicationSettings>
        <NAV_Application_Server.My.MySettings>
            <setting name="NAVAppServer" serializeAs="String">
                <value>http://<Middle Tiere Host Name>:7047/DynamicsNAV/WS/<CompanyName>/Codeunit/NAVAppServer</value>
            </setting>
            <setting name="CodeunitID" serializeAs="String">
                <value>81004</value>
            </setting>
            <setting name="LogMode" serializeAs="String">
                <value>False</value>
            </setting>
            <setting name="TimerInterval" serializeAs="String">
                <value>90000</value>
            </setting>
            <setting name="LogFrequency" serializeAs="String">
                <value>15:00:00</value>
            </setting>
            <setting name="SMTPHost" serializeAs="String">
                <value>mail.dynamics.is</value>
            </setting>
            <setting name="FromAddress" serializeAs="String">
                <value>gunnar@dynamics.is</value>
            </setting>
            <setting name="ToAddress" serializeAs="String">
                <value>gunnar@dynamics.is</value>
            </setting>
            <setting name="RetryTimerInterval" serializeAs="String">
                <value>45000</value>
            </setting>
        </NAV_Application_Server.My.MySettings>
    </applicationSettings>

To install as a service start command prompt in elevated mode and execute installutil.exe command.  The install will prompt for the user to start the service.

C:\Windows\Microsoft.NET\Framework\v2.0.50727>InstallUtil.exe "C:\Program Files(x86)\Dynamics.is\VB.NET NAV Application Server\VB.NET NAV Application Server.exe"

Microsoft (R) .NET Framework Installation utility Version 2.0.50727.5420
Copyright (c) Microsoft Corporation.  All rights reserved.

Running a transacted installation.

Beginning the Install phase of the installation.
See the contents of the log file for the C:\Program Files (x86)\Dynamics.is\VB.NET NAV Application Server\VB.NET NAV Application Server.exe assembly's progress.

The file is located at C:\Program Files (x86)\Dynamics.is\VB.NET NAV Application Server\VB.NET NAV Application Server.InstallLog.
Installing assembly 'C:\Program Files (x86)\Dynamics.is\VB.NET NAV Application Server\VB.NET NAV Application Server.exe'.
Affected parameters are:
   logtoconsole =
   assemblypath = C:\Program Files (x86)\Dynamics.is\VB.NET NAV Application Server\VB.NET NAV Application Server.exe
   logfile = C:\Program Files (x86)\Dynamics.is\VB.NET NAV Application Server\VB.NET NAV Application Server.InstallLog
Installing service VB.NET-NAVAppServer$DEFAULT...
Service VB.NET-NAVAppServer$DEFAULT has been successfully installed.
Creating EventLog source VB.NET-NAVAppServer$DEFAULT in log Application...

The Install phase completed successfully, and the Commit phase is beginning.
See the contents of the log file for the C:\Program Files (x86)\Dynamics.is\VB.NET NAV Application Server\VB.NET NAV Application Server.exe assembly's progress.

The file is located at C:\Program Files (x86)\Dynamics.is\VB.NET NAV Application Server\VB.NET NAV Application Server.InstallLog.
Committing assembly 'C:\Program Files (x86)\Dynamics.is\VB.NET NAV Application Server\VB.NET NAV Application Server.exe'.
Affected parameters are:
   logtoconsole =
   assemblypath = C:\Program Files (x86)\Dynamics.is\VB.NET NAV Application Server\VB.NET NAV Application Server.exe
   logfile = C:\Program Files (x86)\Dynamics.is\VB.NET NAV Application Server\VB.NET NAV Application Server.InstallLog

The Commit phase completed successfully.

The transacted install has completed.

The last step is to change the service startup and start the service.

The service will create entries in the Application Log.

NAV Web Service Codeunit

The following ZIP files are encrypted.

VB.NET NAV Application Server DEFAULT Executables

NAV App Server Visual Studio 2008 Project

Dialog in Dynamics NAV

In so many cases when writing code for Dynamics NAV you want to display a dialog to notify the user or open a progress dialog.  Today, we always need to consider that the code might be running from a web service where the GUIALLOWED variable is set to false.

I created a codeunit to replace the dialog variable type in my code.  That codeunit is attached at the end.  This saves me work and the code is quite simple.
[code htmlscript=”false”]Customer – OnPreDataItem()
DialogInstance.WindowOpen(
‘Reading Customer #1##################\\’ +
‘@2@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@’);
DialogInstance.WindowSetTotal(2,COUNT);

Customer – OnAfterGetRecord()
DialogInstance.WindowProcess(2);
DialogInstance.WindowUpdateText(1,"No.");

Customer – OnPostDataItem()
DialogInstance.WindowClose;[/code]
The Dialog Instance codeunit checks if a dialog can be opened and only updates the progress dialog ten times per second.
[code htmlscript=”false”]WindowProcess(WindowIndex : Integer)
IF NOT GUIALLOWED THEN
EXIT;

Counter[WindowIndex] := Counter[WindowIndex] + 1;

IF NOT DialogIsOpen THEN
EXIT;

IF (CURRENTDATETIME – WindowLastUpdated) > 100 THEN BEGIN
Window.UPDATE(WindowIndex,ROUND(Counter[WindowIndex] / Total[WindowIndex] * 10000,1));
WindowLastUpdated := CURRENTDATETIME;
END;[/code]
DialogInstance

Using Web Services for your NAS jobs

In NAV 7 the NAV Application Server will no longer be supported.  The Job Queue has been redesigned to support the new STARTSESSION feature that will create a new session on the service tier to execute a given task.

In NAV 2009 and going forward it is possible to use web services to act like an application server with the help of a simple program with a timer.

For example a program with a code like this
[code htmlscript=”false” lang=”vb”]Public Class NAVAppServer
Dim Success As Boolean
Dim NAVApp1 As New NAVApp.NAVAppServer
Dim SystemUser As New System.Net.NetworkCredential
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs)
NAVApp1.UseDefaultCredentials = True
NAVApp1.Url = "https://dynamics.is:7047/DynamicsNAS/WS/Dynamics%20Inc/Codeunit/NAVAppServer"
NAVApp1.ExecuteCodeunit(50059, True)

End Sub
End Class[/code]
The Codeunit NAVAppServer is here and also attached
[code htmlscript=”false”]ExecuteCodeunit(CodeunitID : Integer;Log : Boolean) Success : Boolean
LogEntryNo := InsertLogEntry(5,CodeunitID);
Success := CODEUNIT.RUN(CodeunitID);
UpdateLogEntry(LogEntryNo,Success);

InsertLogEntry(ObjectType : ‘,,,Report,,Codeunit’;ObjectNo : Integer) : Integer
WITH JobQueueLogEntry DO BEGIN
INIT;
ID := CREATEGUID;
"User ID" := USERID;
"Start Date/Time" := CURRENTDATETIME;
"Object Type to Run" := ObjectType;
"Object ID to Run" := ObjectNo;
INSERT(TRUE);
COMMIT;
EXIT("Entry No.");
END;

UpdateLogEntry(LogEntryNo : Integer;WasSuccess : Boolean)
WITH JobQueueLogEntry DO BEGIN
GET(LogEntryNo);
"End Date/Time" := CURRENTDATETIME;
IF WasSuccess THEN
Status := Status::Success
ELSE BEGIN
Status := Status::Error;
SetErrorMessage(COPYSTR(GETLASTERRORTEXT,1,1000));
END;
MODIFY;
COMMIT;
END;[/code]
This is published as a web service by adding an entry into table 2000000076 “Web Service”.

NAVAppServerCodeunit

 

WinHTTP and RTC Client

I have been using the automation ‘Microsoft XML, v6.0’.XMLHTTP to communicate with web services and web sites.  I have been experiencing a problem with this automation when running in Role Tailored Client.  The solution has been to use the automation ‘Microsoft XML, v6.0’.ServerXMLHTTP when running in the service tier.
[code htmlscript=”false”]IF ISSERVICETIER THEN BEGIN
IF ISCLEAR(WinHTTPServer) THEN
CREATE(WinHTTPServer,TRUE,FALSE);
WinHTTPServer.open(‘GET’,URL,FALSE);
WinHTTPServer.send(”);

IF WinHTTPServer.status <> 200 THEN
ERROR(Text007,WinHTTPServer.status,WinHTTPServer.statusText);

DOMDocument.load(WinHTTPServer.responseXML);
CLEAR(WinHTTPServer);
END ELSE BEGIN
IF ISCLEAR(WinHTTP) THEN
CREATE(WinHTTP,TRUE,FALSE);
WinHTTP.open(‘GET’,URL,FALSE);
WinHTTP.send(”);

IF WinHTTP.status <> 200 THEN
ERROR(Text007,WinHTTP.status,WinHTTP.statusText);

DOMDocument.load(WinHTTP.responseXML);
CLEAR(WinHTTP);
END;[/code]
Where Error string Text007 is “Status error %1 %2”.

My asp.net website using NAV Web Service

I am building an asp.net website that communicates with NAV via Web Services.  One of the issues I had to solve was the authentication between the web and the NAV Web Services.

You can ether use NTLM authentication or the current user.  If you will be using NTLM you will need code similar to this in you website.
[code htmlscript=”false” land=”vb”]Dim NAVPunch1 As New WebService.NAVPunch
Dim User As New System.Net.NetworkCredential
User.Domain = "Dynamics.is"
User.UserName = "Gunnar"
User.Password = "<password>"
NAVPunch1.Credentials = User
NAVPunch1.Url = "http://Dynamics.is:7047/DynamicsNAV/WS/" & _
"Dynamics/Codeunit/NAVPunch"[/code]
This also means that you have to store the username, domain and password in you web site code. You will also need to enable NTLM authentication in your CustomSettings.xml
[code htmlscript=”false” lang=”vb”]<add key="WebServicesUseNTLMAuthentication" value="true"></add>[/code]
The other way is to use the credentials of the user running the web. In that case the code would be similar to this:
[code htmlscript=”false” lang=”vb”]NAVPunch1.UseDefaultCredentials = True
NAVPunch1.Url = "http://Dynamics.is:7047/DynamicsNAV/WS/" & _
"Dynamics/Codeunit/NAVPunch"[/code]
And no changes to CustomSettings.xml are required.  The authentication will be handled with IIS.  You will need to go into Internet Information Services (IIS) Manager.  Go into Application Pools and add a new application pool

Select a name that fits you web site and then go to Advanced Settings…

and update the Idendity.

Then go and select this Application Pool for the web site.

The final step is to make sure that the user you select in your code or in the application pool has access to NAV Web Services.  That is done with the standard authentication methods in Dynamics NAV.

WSDL Code Generator

On several occasions I have needed to create a code to communicate with Soap Web Services. The manual labor in creating the functions and XML Ports is something that I would like to be rid of. On that node I created a Batch that does most of the job for me.

Execute this and you will be asked to save the “Web Service Objects.txt” file to your computer and from there you will be able to import the file and continue the work.

You will get XMP Ports for every method, both the request and response.  You will also get a Codeunit with a function for every method and the necessary functions to handle the web service communication.

What is left is for you to connect your data to the functions and the XML Ports.

This is not fully tested, so any updates would be appreciated.

Import WSDL (Updated 2012-12-08)

Web Service that deliveres BLOB data

I wanted to be able to use the Record Link table in NAV to link to my attachments. Since that part of NAV is not customizable every attachment that I link to NAV needs to be accessible via URL.

So, when I import or scan a file into NAV it can be stored in a BLOB or I can store it in a customized external database. After storing the file I create a URL link to that file and insert that as a new link to any record in NAV. To complete this task I created a NAV Web Service that delivers the file as a base64 string. Here is the part of the code that encodes the BLOB stream to a BigText variable.
[code htmlscript=”false”]TempFile.CREATETEMPFILE;
TempFileName := TempFile.NAME;
TempFile.CLOSE;
TempFile.TEXTMODE(FALSE);
TempFile.CREATE(TempFileName);
TempFile.CREATEOUTSTREAM(OutStr);
DocumentStore.Blob.CREATEINSTREAM(InStr);
COPYSTREAM(OutStr,InStr);
TempFile.CLOSE;

CREATE(XMLDoc);
CREATE(ADOStream);
XMLNode := XMLDoc.createNode(‘element’, ‘ImageFile’, ‘SKYRR Signing’);
XMLNode.dataType := ‘bin.base64’;

ADOStream.Type := 1;
ADOStream.Open();
ADOStream.LoadFromFile(TempFileName);
XMLNode.nodeTypedValue := ADOStream.Read();
ADOStream.Close();

Document.ADDTEXT(XMLNode.text);

CLEAR(ADOStream);
CLEAR(XMLNode);
CLEAR(XMLDoc);
ERASE(TempFileName);[/code]
Then I created a aps.net website that can get the document both from this NAV Web Service and also from the customized database all based on the parameters passed with the URL.

Attached is a part of the NAV code and the website required to deliver the attachment.

WebSite

Attachment NAV Source Code

 

Employee Attendance via Web Services

In my older version of Time Registration for Dynamics NAV I had ASP web site working on Microsoft SQL server that is used as an attendance list for an office.  On the web site by clicking on the employee name a page with punch in or punch out appears.  From the older system the user had to manually synchronize the external SQL database.

On the new version of Time Registration for Dynamics NAV I am using the new web service support of NAV 2009.  The task was to change the ASP web site and remove the SQL server connection on the web site.  The result is a success, the web site is available here.  The data is directly from Dynamics NAV and all updates are directly inserted into Dynamics NAV.

URL Encoding

I was reading Freddys Blog and found a function to encode URI Component.  I tested the function but found that I needed to change the function to correctly encode my URL.

In Iceland the Company Names in many cases include characters in the range from 128 to 255.  This new function can handle this upper range correctly.

URIEncoder