Connecting to NAV Web Services from C# using Service Reference (code version)

You should read the post about connecting to NAV Web Services from C# using Service Reference (config file version) before continuing here.

Code is king

As you saw in the other post, the config file was pretty complicated and although it is editable by hand and as such could be modified at installtime or whatever, I actually prefer to capture a number of these settings in code and only have very specific values in a config file (like f.ex. the base URL).

In NAV you would never have the full Service URL on all services in a config file. This would mean that you would have to change company in a number of locations in a config file – that just doesn’t fly.

If we have a look at the config file once more, you will see that there is a Binding configuration, specifying a BasicHttpBinding with a couple of settings. If we want to create this binding in code, it would look like:

// Create a NAV comatible binding
BasicHttpBinding navWSBinding = new BasicHttpBinding();
navWSBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
navWSBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;

Having this binding class, we can now create a systemService Service Client simply by:

SystemService_PortClient systemService = new SystemService_PortClient(navWSBinding, new EndpointAddress(baseURL + “SystemService”));

Specifying the endpoint address to the constructor.

The only other thing we need to specify is the endpoint behavior to allow delegation. This is done in code by:

systemService.ClientCredentials.Windows.AllowedImpersonationLevel =
System.Security.Principal.TokenImpersonationLevel.Delegation;
systemService.ClientCredentials.Windows.AllowNtlm = true;

Using code like this actually makes the app.config obsolete and you can delete the config file totally when running the below application.

The entire application

The following is a listing of the full console application using code to set all properties and no app.config is necessary (nor used at all):

using System;
using System.Net;
using testAppWCF.SystemServiceRef;
using testAppWCF.CustomerPageRef;
using System.ServiceModel;

namespace testAppWCF
{
class Program
{
static void Main(string[] args)
{
string baseURL = “
http://localhost:7047/DynamicsNAV/WS/”;

            // Create a NAV compatible binding
BasicHttpBinding navWSBinding = new BasicHttpBinding();
navWSBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
navWSBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;

            // Create the SystemService Client
SystemService_PortClient systemService = new SystemService_PortClient(navWSBinding, new EndpointAddress(baseURL + “SystemService”));
systemService.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Delegation;
systemService.ClientCredentials.Windows.AllowNtlm = true;

            Console.WriteLine(“Companies:”);
string[] companies = systemService.Companies();
foreach(string company in companies)
Console.WriteLine(company);
string cur = companies[0];

            string customerPageURL = baseURL + Uri.EscapeDataString(cur) + “/Page/Customer”;
Console.WriteLine(“nURL of Customer Page: “+customerPageURL);

            // Create the Customer Page Service Client
Customer_PortClient customerService = new Customer_PortClient(navWSBinding, new EndpointAddress(customerPageURL));
customerService.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Delegation;
customerService.ClientCredentials.Windows.AllowNtlm = true;

            Customer customer10000 = customerService.Read(“10000”);
Console.WriteLine(“nName of Customer 10000: “+customer10000.Name);

            Customer_Filter filter1 = new Customer_Filter();
filter1.Field = Customer_Fields.Country_Region_Code;
filter1.Criteria = “GB”;

            Customer_Filter filter2 = new Customer_Filter();
filter2.Field = Customer_Fields.Location_Code;
filter2.Criteria = “RED|BLUE”;

            Console.WriteLine(“nCustomers in GB served by RED or BLUE warehouse:”);
Customer_Filter[] filters = new Customer_Filter[] { filter1, filter2 };
Customer[] customers = customerService.ReadMultiple(filters, null, 0);
foreach (Customer customer in customers)
Console.WriteLine(customer.Name);

            Console.WriteLine(“nTHE END”);
Console.ReadLine();
}
}
}

If you need to specify a different username / password it is done in the same way as it was done for Service References using config files.

This application will output exactly the same as the application using Service References and a config file, in the end the config file is just a number of settings which will be used to instantiate a number of classes from – giving the same result.

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Connecting to NAV Web Services from C# using Service Reference (config file version)

Prerequisites

Please read this post to get a brief explanation of the scenario I will implement in C# using Web References.

For C# we can leave the Service Tier running Negotiate or we can use Ntlm as PHP and Java. In this example I will assume that the Service Tier is running SPNEGO (which is the default)

BTW. Basic knowledge about C# is required to understand the following post:-)

Version and download

I am using Visual Studio 2008 professional with SP1 when writing this sample, to be honest I have NOT tried to see whether this will work in the Express versions of Visual Studio, but they do have Service Reference and Web Reference so I cannot see why not.

What is the difference between a Web Reference and a Service Reference?

In short, the Web Reference is a .net 2.0 compatible Web Service reference, the Service Reference is a .net 3.5 WCF based Service Reference.

Add Web Reference is a wrapper over wsdl.exe and can be used to create proxies for .NET 1.1 or 2.0 clients. Of course this means when you are pointing to a WCF service you have to be pointing to an endpoint that uses basicHttpBinding.

Add Service Reference is a wrapper over svcutil.exe and also creates clients proxies. These proxies, however, can only be consumed by .NET 3.5 clients.

In this post I will describe how to use Service References, where all settings are stored in the .config file.

How to add a Service Reference

Select Add Service Reference

image_thumb[13]

Type the URL and namespace for the SystemService Service Reference:

image

and for the Customer Page Service Reference:

image

The .config file

After having added these Service References, all the properties and settings about the references are stored in app.config (which gets autocreated by Visual Studio). The Proxy classes are generated and everything seems fine until you start using it.

You have to change a couple of things in the app.config file before using these.

Under every binding configuration setting you will find a section like this:

<security mode=”None”>
<transport clientCredentialType=”None” proxyCredentialType=”None”
realm=””>
<extendedProtectionPolicy policyEnforcement=”Never” />
</transport>
<message clientCredentialType=”UserName” algorithmSuite=”Default” />
</security>

this does not match whats needed for NAV Web Services. NAV Web Services absolutely do not run without security. You will have to change this section with:

<security mode=”TransportCredentialOnly”>
<transport clientCredentialType=”Windows”  />
</security>

which matches the security mode and transport of the binding used by NAV when using Windows Authentication (SPNEGO). If the Service Tier is setup to run Ntlm – the ClientCredentialType needs to be “Ntlm” in the config file.

Furthermore you will have to add a behavior indicating that the Web Service Listener is allowed to use Delegation on your credentials (between </bindings> and <client>:

<behaviors>
<endpointBehaviors>
<behavior name=”allowDelegation”>
<clientCredentials>
<windows allowedImpersonationLevel=”Delegation”
allowNtlm=”true”/>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>

and last, but not least you will have to add this behavior to all endpoints like:

<endpoint address=”http://localhost:7047/DynamicsNAV/WS/CRONUS%20International%20Ltd/Page/Customer”
          binding=”basicHttpBinding”
bindingConfiguration=”Customer_Binding”
behaviorConfiguration=”allowDelegation”
contract=”CustomerPageRef.Customer_Port”
name=”Customer_Port” />

If we strip away all the unnecessary defaults and modify a couple of things by hand, the ENTIRE config file could look like this:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name=”NavWSBinding”>
<security mode=”TransportCredentialOnly”>
<transport clientCredentialType=”Windows”  />
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name=”allowDelegation”>
<clientCredentials>
<windows allowedImpersonationLevel=”Delegation”
allowNtlm=”true”/>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address=”
http://localhost:7047/DynamicsNAV/WS/CRONUS%20International%20Ltd/Page/Customer”
                binding=”basicHttpBinding”
bindingConfiguration=”NavWSBinding”
behaviorConfiguration=”allowDelegation”
contract=”CustomerPageRef.Customer_Port”
name=”Customer_Port” />
<endpoint address=”
http://localhost:7047/DynamicsNAV/WS/SystemService”
                binding=”basicHttpBinding”
bindingConfiguration=”NavWSBinding”
behaviorConfiguration=”allowDelegation”
contract=”SystemServiceRef.SystemService_Port”
name=”SystemService_Port” />
</client>
</system.serviceModel>
</configuration>

Confused?

The code

First a couple of using statements (including the two reference namespaces) and the main body of a console app:

using System;
using System.Net;
using testAppWCF2.SystemServiceRef;
using testAppWCF2.CustomerPageRef;

namespace testAppWCF2
{
class Program
{
static void Main(string[] args)
{
// main program code
}
}
}

The main code follows.

First, connect to the System Web Service and list all companies:

string baseURL = “http://localhost:7047/DynamicsNAV/WS/”;

// Create the SystemService Client
SystemService_PortClient systemService = new SystemService_PortClient(“SystemService_Port”, baseURL + “SystemService”);

Console.WriteLine(“Companies:”);
string[] companies = systemService.Companies();
foreach (string company in companies)
Console.WriteLine(company);
string cur = companies[0];

Note, that when creating a System Service Client, I specify the name of an endpoint configuration and a URL. I didn’t have to specify anything, then all defaults would be taken from the Config file, but I like to show how you can calculate the URL and specify that at runtime.

Now I have the company I want to use in cur and the way I create a URL to the Customer page is by doing:

string customerPageURL = baseURL + Uri.EscapeDataString(cur) + “/Page/Customer”;
Console.WriteLine(“nURL of Customer Page: ” + customerPageURL);

and then I can create a Service Class to the Customer Page by specifying the config section and a URL again:

// Create the SystemService Client
Customer_PortClient customerService = new Customer_PortClient(“Customer_Port”, customerPageURL);

and using this, I read customer 10000 and output the name:

Customer customer10000 = customerService.Read(“10000”);
Console.WriteLine(“nName of Customer 10000: ” + customer10000.Name);

Last, but not least – lets create a filter and read all customers in GB that has Location Code set to RED or BLUE:

Customer_Filter filter1 = new Customer_Filter();
filter1.Field = Customer_Fields.Country_Region_Code;
filter1.Criteria = “GB”;

Customer_Filter filter2 = new Customer_Filter();
filter2.Field = Customer_Fields.Location_Code;
filter2.Criteria = “RED|BLUE”;

Console.WriteLine(“nCustomers in GB served by RED or BLUE warehouse:”);
Customer_Filter[] filters = new Customer_Filter[] { filter1, filter2 };
Customer[] customers = customerService.ReadMultiple(filters, null, 0);
foreach (Customer customer in customers)
Console.WriteLine(customer.Name);

Console.WriteLine(“nTHE END”);
Console.ReadLine();

As you can see the code is actually as simple as the Web Reference version, but the config file complicates things a lot. All of the above will output the following to a console prompt (on my machine running NAV 2009SP1 W1)

image

Authentication

BTW – this sample will by default run Windows Authentication. If you want to specify a different user you will need to set the ClientCredential property like this:

customerService.ClientCredentials.Windows.ClientCredential = new NetworkCredential(“user”, “password”, “domain”);

You would need to set this on each Service Client you create.

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Connecting to NAV Web Services from C# using Web Reference

Prerequisites

Please read this post to get a brief explanation of the scenario I will implement in C# using Web References.

For C# we can leave the Service Tier running Negotiate or we can use Ntlm as PHP and Java. In this example I will assume that the Service Tier is running SPNEGO (which is the default)

BTW. Basic knowledge about C# is required to understand the following post:-)

Version and download

I am using Visual Studio 2008 professional with SP1 when writing this sample, to be honest I have NOT tried to see whether this will work in the Express versions of Visual Studio, but they do have Service Reference and Web Reference so I cannot see why not.

What is the difference between a Web Reference and a Service Reference?

In short, the Web Reference is a .net 2.0 compatible Web Service reference, the Service Reference is a .net 3.5 WCF based Service Reference.

Add Web Reference is a wrapper over wsdl.exe and can be used to create proxies for .NET 1.1 or 2.0 clients. Of course this means when you are pointing to a WCF service you have to be pointing to an endpoint that uses basicHttpBinding.

Add Service Reference is a wrapper over svcutil.exe and also creates clients proxies. These proxies, however, can only be consumed by .NET 3.5 clients.

How to add Web References

Select Add Service Reference

image

Click Advanced

image

Click Add Web Reference

image

Type in the URL and specify the namespace for the SystemService Web Reference

image

and for the Customer Page Web Reference

image

When adding a Web Reference, Visual Studio will create a config file in which it stores stuff like the URL for the Reference. In my samples I will set the URL in code and due to this, the config file is not needed.

Authentication

In the following sample I use Windows Authentication. In Web References you just need to set the property UseDefaultCredentials in the service class to true, then .net will automatically try to use your windows credentials to connect to the Web Service.

If you want to connect to a Web Reference using a specific username/password you need to exchange this line:

someService.UseDefaultCredentials = true;

with this:

someService.Credentials = new NetworkCredential(“user”, “password”, “domain”);

The code

First a couple of using statements (including the two reference namespaces) and the main body of a console app:

using System;
using System.Net;
using testApp.CustomerPageRef;
using testApp.SystemServiceRef;

namespace testApp
{
class Program
{
static void Main(string[] args)
{
// main program code
        }
    }
}

The main code follows

First, connect to the System Web Service and list all companies:

string baseURL = “http://localhost:7047/DynamicsNAV/WS/”;

SystemService systemService = new SystemService();
systemService.Url = baseURL + “SystemService”;
systemService.UseDefaultCredentials = true;

Console.WriteLine(“Companies:”);
string[] companies = systemService.Companies();
foreach (string company in companies)
Console.WriteLine(company);
string cur = companies[0];

Now I have the company I want to use in cur and the way I create a URL to the Customer page is by doing:

string customerPageURL = baseURL + Uri.EscapeDataString(cur) + “/Page/Customer”;
Console.WriteLine(“nURL of Customer Page: ” + customerPageURL);

and then I can create a Service Class to the Customer Page:

Customer_Service customerService = new Customer_Service();
customerService.Url = customerPageURL;
customerService.UseDefaultCredentials = true;

and using this, I read customer 10000 and output the name:

Customer cust10000 = customerService.Read(“10000”);
Console.WriteLine(“nName of Customer 10000: ” + cust10000.Name);

Last, but not least – lets create a filter and read all customers in GB that has Location Code set to RED or BLUE:

Customer_Filter filter1 = new Customer_Filter();
filter1.Field = Customer_Fields.Country_Region_Code;
filter1.Criteria = “GB”;

Customer_Filter filter2 = new Customer_Filter();
filter2.Field = Customer_Fields.Location_Code;
filter2.Criteria = “RED|BLUE”;

Console.WriteLine(“nCustomers in GB served by RED or BLUE warehouse:”);
Customer_Filter[] filters = new Customer_Filter[] { filter1, filter2 };
Customer[] customers = customerService.ReadMultiple(filters, null, 0);
foreach (Customer customer in customers)
Console.WriteLine(customer.Name);

Console.WriteLine(“nTHE END”);
Console.ReadLine();

All of the above will output the following to a console prompt (on my machine running NAV 2009SP1 W1)

image

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Connecting to NAV Web Services from Java

Prerequisites

Please read this post to get an explanation on how to modify the service tier to use NTLM authentication and for a brief explanation of the scenario I will implement in Java.

BTW. Basic knowledge about Java is required to understand the following post:-)

Version and download

Java does not natively have support for the SPNEGO authentication protocol, but it does have support for NTLM authentication, so you will have to change the Web Services listener to use that.

I downloaded the Java developer toolkit from http://www.java.com/en/download/manual.jsp and installed it under c:\sun – and made sure that c:\sun\SDK\bin is added to the path.

wsimport – import WSDL

To make Java able to see and use webservices I use wsimport to create proxy classes in Java. In a directory, I have created two empty folders (generated and source) and use the following command:

C:\java>wsimport -d generated -s source http://localhost:7047/DynamicsNAV/WS/SystemService

this will create a series of .class files under

C:\java\generated\schemas\dynamics\microsoft\nav\system

and a set of source files under

C:\java\source\schemas\dynamics\microsoft\nav\system

note, that the folder structure is the namespace of the web service.

javac – compile the program

Assuming that I create a test program in c:\java\test.java, the following command will compile the program:

c:\java>c:\sun\sdk\jdk\bin\javac -cp .;generated test.java

this creates a c:\java\test.class, which we can run.

java – run the program

The statement used to run the java class is:

c:\java>c:\sun\sdk\jdk\bin\java -cp .;generated test

enough about setup – lets code…

Authentication

By default, java supports Windows Authentication and if the class is running in context of a windows user, these credentials are automatically used to authenticate towards NAV Web Services.

If you want to use different credentials (connect as a different user) you will need to create a class derived from Authenticator and implement getPasswordAuthentication like:

static class MyAuthenticator extends Authenticator {
public PasswordAuthentication getPasswordAuthentication() {
return (new PasswordAuthentication(“domainuser”, new char[] {‘p’,’a’,’s’,’s’,’w’,’o’,’r’,’d’}));
}
}

and then as the very first statement in the class

Authenticator.setDefault(new MyAuthenticator());

test.java

First of all, I have a number of imports. Not sure that they all are needed and then a main function body:

import java.net.*;
import java.util.*;
import java.io.*;
import javax.xml.namespace.QName;
import schemas.dynamics.microsoft.nav.system.*;
import schemas.dynamics.microsoft.page.customer.*;

public class test {
public static void main(String[] args) {

        // main program code

    }
}

The main code is:

try {
String baseURL = “http://localhost:7047/DynamicsNAV/WS/&#8221;;

The following code allows you to connect to NAV Web Services system service in Java and output the companies available on the service tier:

  URL systemServiceURL = new URL(baseURL + “SystemService”);
QName systemServiceQName = new QName(“urn:microsoft-dynamics-schemas/nav/system/”, “SystemService”);
SystemService systemService = new SystemService(systemServiceURL, systemServiceQName);
SystemServicePort systemPort = systemService.getSystemServicePort();
List companies = systemPort.companies();
System.out.println(“Companies:”);
for(String company : companies) {
System.out.println(company);
}
String cur = companies.get(0);

Now I have the company I want to use in cur and the way I create a URL to the Customer page is by doing:

  URL customerPageURL = new URL(baseURL+URLEncoder.encode(cur,”UTF-8″).replace(“+”,”%20″)+”/Page/Customer”);
QName customerPageQName = new QName(“urn:microsoft-dynamics-schemas/page/customer”, “Customer_Service”);

  System.out.println(“nURL of Customer page:” + customerPageURL.toString());

and then I can create a Service Client to the Customer Page:

  CustomerService customerService = new CustomerService(customerPageURL, customerPageQName);
CustomerPort customerPort = customerService.getCustomerPort();

and using this, I read customer 10000 and output the name:

  Customer cust = customerPort.read(“10000”);
System.out.println(“nName of Customer 10000:” + cust.getName());

Last, but not least – lets create a filter and read all customers in GB that has Location Code set to RED or BLUE:

  CustomerFilter filter1 = new CustomerFilter();
filter1.setField(CustomerFields.fromValue(“Location_Code”));
filter1.setCriteria(“RED|BLUE”);

  CustomerFilter filter2 = new CustomerFilter();
filter2.setField(CustomerFields.fromValue(“Country_Region_Code”));
filter2.setCriteria(“GB”);

  List filters = new ArrayList();
filters.add(filter1);
filters.add(filter2);

  System.out.println(“nCustomers in GB served by RED or BLUE warehouse:”);
CustomerList customers = customerPort.readMultiple(filters, null, 0);
for (Customer c : customers.getCustomer()) {
System.out.println(c.getName());
}

  System.out.println(“nTHE END”);

} catch(MalformedURLException e) {
} catch(UnsupportedEncodingException e) {
}

All of the above will output the following in a prompt (on my machine running NAV 2009SP1 W1)

image

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Connecting to NAV Web Services from PHP

Prerequisites

Please read this post to get an explanation on how to modify the service tier to use NTLM authentication and for a brief explanation of the scenario I will implement in PHP.

BTW. Basic knowledge about PHP is required to understand the following post:-)

Version and download

In my sample I am using PHP version 5.2.11, which I downloaded from http://www.php.net, but it should work with any version after that.

In order to make this work you need to make sure that SOAP and CURL extensions are installed and enabled in php.ini.

PHP does not natively have support for NTLM nor SPNEGO authentication protocols, so we need to implement that manually. Now that sounds like something, which isn’t straightforward and something that takes an expert.   Fortunately there are a lot of these experts on the internet and I found this post (written by Thomas Rabaix), which explains about how what’s going on, how and why. Note that this implements NTLM authentication and you will have to change the Web Services listener to use that.

License of the code

The code can be used freely as long as you include Thomas’ copyright notice in the code.

/*
* Copyright (c) 2008 Invest-In-France Agency http://www.invest-in-france.org
*
* Author : Thomas Rabaix
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

“My” Version

I have modified Thomas’ version slightly (primarily removing debug echo’s etc.).

I have also changed the way the username and password is specified to be a script constant:

define(‘USERPWD’, ‘domainuser:password’);

The Stream wrapper now looks like:

class NTLMStream
{
private $path;
private $mode;
private $options;
private $opened_path;
private $buffer;
private $pos;
/**
* Open the stream
*
* @param unknown_type $path
* @param unknown_type $mode
* @param unknown_type $options
* @param unknown_type $opened_path
* @return unknown
*/
public function stream_open($path, $mode, $options, $opened_path) {
$this->path = $path;
$this->mode = $mode;
$this->options = $options;
$this->opened_path = $opened_path;
$this->createBuffer($path);
return true;
}
/**
* Close the stream
*
*/
public function stream_close() {
curl_close($this->ch);
}
/**
* Read the stream
*
* @param int $count number of bytes to read
* @return content from pos to count
*/
public function stream_read($count) {
if(strlen($this->buffer) == 0) {
return false;
}
$read = substr($this->buffer,$this->pos, $count);
$this->pos += $count;
return $read;
}
/**
* write the stream
*
* @param int $count number of bytes to read
* @return content from pos to count
*/
public function stream_write($data) {
if(strlen($this->buffer) == 0) {
return false;
}
return true;
}
/**
*
* @return true if eof else false
*/
public function stream_eof() {
return ($this->pos > strlen($this->buffer));
}
/**
* @return int the position of the current read pointer
*/
public function stream_tell() {
return $this->pos;
}
/**
* Flush stream data
*/
public function stream_flush() {
$this->buffer = null;
$this->pos = null;
}
/**
* Stat the file, return only the size of the buffer
*
* @return array stat information
*/
public function stream_stat() {
$this->createBuffer($this->path);
$stat = array(
‘size’ => strlen($this->buffer),
);
return $stat;
}
/**
* Stat the url, return only the size of the buffer
*
* @return array stat information
*/
public function url_stat($path, $flags) {
$this->createBuffer($path);
$stat = array(
‘size’ => strlen($this->buffer),
);
return $stat;
}
/**
* Create the buffer by requesting the url through cURL
*
* @param unknown_type $path
*/
private function createBuffer($path) {
if($this->buffer) {
return;
}
$this->ch = curl_init($path);
curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($this->ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
curl_setopt($this->ch, CURLOPT_USERPWD, USERPWD);
$this->buffer = curl_exec($this->ch);
$this->pos = 0;
}
}

The NTLM SOAP Client also uses the USERPWD constant defined above and looks like:

class NTLMSoapClient extends SoapClient {
function __doRequest($request, $location, $action, $version) {
$headers = array(
‘Method: POST’,
‘Connection: Keep-Alive’,
‘User-Agent: PHP-SOAP-CURL’,
‘Content-Type: text/xml; charset=utf-8’,
‘SOAPAction: “‘.$action.'”‘,
);
$this->__last_request_headers = $headers;
$ch = curl_init($location);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, true );
curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
curl_setopt($ch, CURLOPT_USERPWD, USERPWD);
$response = curl_exec($ch);
return $response;
}

    function __getLastRequestHeaders() {
return implode(“n”, $this->__last_request_headers).”n”;
}
}

Putting this into the PHP script now allows you to connect to NAV Web Services system service in PHP and output the companies available on the service tier:

// we unregister the current HTTP wrapper
stream_wrapper_unregister(‘http’);
// we register the new HTTP wrapper
stream_wrapper_register(‘http’, ‘NTLMStream’) or die(“Failed to register protocol”);

// Initialize Soap Client
$baseURL = ‘
http://localhost:7047/DynamicsNAV/WS/’;
$client = new NTLMSoapClient($baseURL.’SystemService’);

// Find the first Company in the Companies
$result = $client->Companies();
$companies = $result->return_value;
echo “Companies:<br>”;
if (is_array($companies)) {
foreach($companies as $company) {
echo “$company<br>”;
}
$cur = $companies[0];
}
else {
echo “$companies<br>”;
$cur = $companies;
}

Note that is return value is an array if there are multiple companies, but a company name if there is only one. I have NO idea why this is or whether I can write the code differently to avoid this.

Now I have the company I want to use in $cur and the way I create a URL to the Customer page is by doing:

$pageURL = $baseURL.rawurlencode($cur).’/Page/Customer’;
echo “<br>URL of Customer page: $pageURL<br><br>”;

and then I can create a Soap Client to the Customer Page:

// Initialize Page Soap Client
$page = new NTLMSoapClient($pageURL);

and using this, I read customer 10000 and output the name:

$params = array(‘No’ => ‘10000’);
$result = $page->Read($params);
$customer = $result->Customer;
echo “Name of Customer 10000:”.$customer->Name.”<br><br>”;

Last, but not least – lets create a filter and read all customers in GB that has Location Code set to RED or BLUE:

$params = array(‘filter’ => array(
array(‘Field’ => ‘Location_Code’,
‘Criteria’ => ‘RED|BLUE’),
array(‘Field’ => ‘Country_Region_Code’,
‘Criteria’ => ‘GB’)
),
‘setSize’ => 0);
$result = $page->ReadMultiple($params);
$customers = $result->ReadMultiple_Result->Customer;

Note that Bookmark is an optional parameter and doesn’t need to be specified.

Now echo the customers and restore the http protocol – again, the return value might be an array and might not.

echo “Customers in GB served by RED or BLUE warehouse:<br>”;

if (is_array($customers)) {
foreach($customers as $cust) {
echo $cust->Name.”<br>”;
}
}
else {
echo $customers->Name.”<br>”;
}

// restore the original http protocole
stream_wrapper_restore(‘http’);

All of the above will output this when the script is opened in a browser (on my machine running NAV 2009SP1 W1)

image_thumb[3]

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Connecting to NAV Web Services from …

I promised to write some posts about how to connect to NAV Web Services from various other programming languages/platforms and I guess it is about time I kept my promise.

I will create a couple of posts on how to connect to NAV Web Services from:

  • PHP
  • Java
  • C# using Web Reference
  • C# using Service Reference
  • Javascript
  • Visual Basic using Web Reference
  • Visual Basic using Service Reference
  • Windows Mobile
  • Microsoft Dynamics NAV 2009SP1
  • and more…

Scenario

For all these I will create some code that:

  • Connects to NAV Web Services System Service and enumerates the companies in the database
  • Constructs a URL to the Customer Page (based on the first company in the list)
  • Get the name of Customer number 10000
  • Get the Customers in GB that have Location Code set to RED or BLUE.

Here is an example of the output of the PHP web site:

image

Note, that the code is not necessarily perfect PHP or Java code and the intention is not to teach people how to code in these languages (I would be a bad teacher) but more to overcome some of the obstacles when trying to work with NAV.

Authentication

The very first obstacle is authentication. As you probably know, NAV 2009 only supported SPNEGO (Negotiate) authentication and both PHP and Java doesn’t currently have any support natively for that.

In NAV 2009 SP1 we added a key in the Service Tier configuration file called WebServicesUseNTLMAuthentication. If you set this key to true the Web Services listener will only use NTLM authentication and as such be easier accessible from other systems.

The config file you need to modify is placed in the directory where the Service Tier executable is and is called CustomSettings.config. The section you are looking for is:

    <!–
Turns on or off NTLM authentication protocol for Web Services
false: Use SPNEGO (recommended)
true: Use NTLM only
–>
<add key=”WebServicesUseNTLMAuthentication” value=”true”></add>

Note that .net works with both values of this settings.

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Open .net SDK command prompt here!

Barry Fitzgerald from Gumdrop books (a NAV 2009 customer) sent me a hint to another blog post, which in short talks about how to add a menu item to your folder menus like this:

image

http://www.theproblemsolver.nl/dotnet_faq_0016.htm

This would open a command prompt, starting in this directory ready to use Visual Studio tools like SN (which you need for Client extensibility) or RegAsm (which you need for COM development).

I found it pretty nice, so I thought I would share this.

On my machine, I have created the following registry key, which seems to work great.

image

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV