Quantcast
Channel: Freddys Blog
Viewing all 161 articles
Browse latest View live

Sending data to NAV through a XMLPort

$
0
0

One of the things I showed at Directions US 2009 was how to send data to NAV via a XMLPort, and I promised to blog about that as well. In the meantime I found out, that Lars actually already wrote about this (and he also made a WebCast).

Please have a look at the NAV Team Blog for more info:

http://blogs.msdn.com/nav/archive/2009/11/06/using-dataports-with-web-services.aspx

The only reason for this post is, that people who attended my session at Directions might look for stuff like this on my blog – so, here you are.

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV


Open .net SDK command prompt here!

$
0
0

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

Connecting to NAV Web Services from …

$
0
0

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

Connecting to NAV Web Services from PHP

$
0
0

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', 'domain\user: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 Java

$
0
0

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("domain\user", 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/";

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<String> 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<CustomerFilter> filters = new ArrayList<CustomerFilter>();
  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 C# using Web Reference

$
0
0
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 C# using Service Reference (config file version)

$
0
0
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 Service Reference (code version)

$
0
0

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 Visual Basic .net using Web Reference

$
0
0

This post is really just a Visual Basic version of this post, please read that post before continuing.

Note, this is my very first Visual Basic application. I don’t think there are any ways to do this easier – but then again – how should I know.

I am creating a Visual Basic Console application and adding two Web References (like it is done in this post) and then it is really just writing the code

The code

Module Module1

    Sub Main()

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

        Dim baseURL As String = "http://localhost:7047/DynamicsNAV/WS/"

        Dim systemService As New SystemServiceRef.SystemService()
        systemService.Url = baseURL + "SystemService"
        systemService.UseDefaultCredentials = True
        Dim companies() As String = systemService.Companies()
        Console.WriteLine("Companies:")
        For Each company As String In companies
            Console.WriteLine(company)
        Next
        Dim cur As String = 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:

        Dim customerPageURL As String = baseURL + Uri.EscapeDataString(cur) + "/Page/Customer"
        Console.WriteLine(vbCrLf + "URL of Customer Page: " + customerPageURL)

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

        Dim customerService As New CustomerPageRef.Customer_Service()
        customerService.Url = customerPageURL
        customerService.UseDefaultCredentials = True

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

        Dim customer10000 As CustomerPageRef.Customer = customerService.Read("10000")
        Console.WriteLine(vbCrLf + "Name 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:

        Dim filter1 As New CustomerPageRef.Customer_Filter()
        filter1.Field = CustomerPageRef.Customer_Fields.Country_Region_Code
        filter1.Criteria = "GB"

        Dim filter2 As New CustomerPageRef.Customer_Filter()
        filter2.Field = CustomerPageRef.Customer_Fields.Location_Code
        filter2.Criteria = "RED|BLUE"

        Console.WriteLine(vbCrLf + "Customers in GB served by RED or BLUE warehouse:")
        Dim filters() As CustomerPageRef.Customer_Filter = New CustomerPageRef.Customer_Filter(1) {filter1, filter2}
        Dim customers() As CustomerPageRef.Customer = customerService.ReadMultiple(filters, Nothing, 0)
        For Each customer As CustomerPageRef.Customer In customers
            Console.WriteLine(customer.Name)
        Next

        Console.WriteLine(vbCrLf + "THE END")
        Console.ReadLine()
    End Sub

End Module

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Connecting to NAV Web Services from Visual Basic .net using Service Reference

$
0
0

This post is really just a Visual Basic version of this post and this post combined, please read those posts before continuing this post.

As described in the other posts, there are two ways to work with Service References – one is to keep the configuration in a .config file and the other is to do everything from code.

Using the .config file

First I create a Visual Basic Console application and add the two service references as explained in the other posts. Then I copied the ServiceModel part of the .config file from this post and pasted it into (and overwrite) the ServiceModel part of my new .config file.

After that, it is really just to write the code

Module Module1

    Sub Main()
        Dim baseURL As String = "
http://localhost:7047/DynamicsNAV/WS/"

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

        Dim systemService As New SystemServiceRef.SystemService_PortClient("SystemService_Port", baseURL + "SystemService")
        Dim companies() As String = systemService.Companies()
        Console.WriteLine("Companies:")
        For Each company As String In companies
            Console.WriteLine(company)
        Next
        Dim cur As String = 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:

        Dim customerPageURL As String = baseURL + Uri.EscapeDataString(cur) + "/Page/Customer"
        Console.WriteLine(vbCrLf + "URL of Customer Page: " + customerPageURL)

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

        Dim customerService As New CustomerPageRef.Customer_PortClient("Customer_Port", customerPageURL)

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

        Dim customer10000 As CustomerPageRef.Customer = customerService.Read("10000")
        Console.WriteLine(vbCrLf + "Name 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:

        Dim filter1 As New CustomerPageRef.Customer_Filter()
        filter1.Field = CustomerPageRef.Customer_Fields.Country_Region_Code
        filter1.Criteria = "GB"

        Dim filter2 As New CustomerPageRef.Customer_Filter()
        filter2.Field = CustomerPageRef.Customer_Fields.Location_Code
        filter2.Criteria = "RED|BLUE"

        Console.WriteLine(vbCrLf + "Customers in GB served by RED or BLUE warehouse:")
        Dim filters() As CustomerPageRef.Customer_Filter = New CustomerPageRef.Customer_Filter(1) {filter1, filter2}
        Dim customers() As CustomerPageRef.Customer = customerService.ReadMultiple(filters, Nothing, 0)
        For Each customer As CustomerPageRef.Customer In customers
            Console.WriteLine(customer.Name)
        Next

        Console.WriteLine(vbCrLf + "THE END")
        Console.ReadLine()
    End Sub

End Module

Using Visual Basic code

If we want to avoid the .config file, the trick is very much like this post to create the configuration in code.

Basically with the above solution, delete the app.config file and change the code to

Module Module1

    Sub Main()
        Dim baseURL As String = "
http://localhost:7047/DynamicsNAV/WS/"

        Dim navWSBinding As New System.ServiceModel.BasicHttpBinding()
        navWSBinding.Security.Mode = ServiceModel.BasicHttpSecurityMode.TransportCredentialOnly
        navWSBinding.Security.Transport.ClientCredentialType = ServiceModel.HttpClientCredentialType.Windows

        Dim systemService As New SystemServiceRef.SystemService_PortClient(navWSBinding, New System.ServiceModel.EndpointAddress(baseURL + "SystemService"))
        systemService.ClientCredentials.Windows.AllowedImpersonationLevel = Security.Principal.TokenImpersonationLevel.Delegation
        systemService.ClientCredentials.Windows.AllowNtlm = True

        Dim companies() As String = systemService.Companies()
        Console.WriteLine("Companies:")
        For Each company As String In companies
            Console.WriteLine(company)
        Next
        Dim cur As String = companies(0)

        Dim customerPageURL As String = baseURL + Uri.EscapeDataString(cur) + "/Page/Customer"
        Console.WriteLine(vbCrLf + "URL of Customer Page: " + customerPageURL)

        Dim customerService As New CustomerPageRef.Customer_PortClient(navWSBinding, New System.ServiceModel.EndpointAddress(customerPageURL))
        customerService.ClientCredentials.Windows.AllowedImpersonationLevel = Security.Principal.TokenImpersonationLevel.Delegation
        customerService.ClientCredentials.Windows.AllowNtlm = True

        Dim customer10000 As CustomerPageRef.Customer = customerService.Read("10000")
        Console.WriteLine(vbCrLf + "Name of Customer 10000: " + customer10000.Name)

        Dim filter1 As New CustomerPageRef.Customer_Filter()
        filter1.Field = CustomerPageRef.Customer_Fields.Country_Region_Code
        filter1.Criteria = "GB"

        Dim filter2 As New CustomerPageRef.Customer_Filter()
        filter2.Field = CustomerPageRef.Customer_Fields.Location_Code
        filter2.Criteria = "RED|BLUE"

        Console.WriteLine(vbCrLf + "Customers in GB served by RED or BLUE warehouse:")
        Dim filters() As CustomerPageRef.Customer_Filter = New CustomerPageRef.Customer_Filter(1) {filter1, filter2}
        Dim customers() As CustomerPageRef.Customer = customerService.ReadMultiple(filters, Nothing, 0)
        For Each customer As CustomerPageRef.Customer In customers
            Console.WriteLine(customer.Name)
        Next

        Console.WriteLine(vbCrLf + "THE END")
        Console.ReadLine()
    End Sub

End Module

In both cases you can change the user used to connect to Web Services by setting service.ClientCredentials.Windows.ClientCredential to an instance of System.Net.NetworkCredential like:

systemService.ClientCredentials.Windows.ClientCredential = New System.Net.NetworkCredential("user", "password", "domain")

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Connecting to NAV Web Services from Javascript

$
0
0

Prerequisites

Please read this post to get a brief explanation of the scenario I will implement in Javascript.

BTW. Basic knowledge about Javascript and XML is required to understand the following post:-)

Browser compatibility

The sample in this post will work with Internet Explorer. I have only tried this with IE8, but according to documentation it should work from IE5 and up.

I actually also tried to download Mozilla FireFox 3.6 and Opera 10.10 – but I failed to make the script work – not due to incompatibility in Javascript, but I simply couldn’t get any of those browsers to authenticate towards NAV WebServices (whether I was running NTLM or SPNEGO – see this post).

This might be due to the XmlHttpRequest being unable to connect to Web Services X-Domain (explained here) – if you really need to make this work, it seems like a possible solution is to create a proxy (another Web Service in any serviceside language) which is hosted on the same site as the Javascript app and forwards all requests.

IE seems to not care if you have trusted the web site (which is the case here). Script based Web Services access should only be used in your local intranet anyway – you should never try to go across the internet and connect to a NAV server somewhere.

Other posts with Javascript code

On my blog there are a number of posts, where I use Javascript to connect to Web Services. All the Gadgets and the Virtual Earth web site are using Javascript to connect to Web Services and in all of these I am using the MSXML ActiveXObject and all of the other samples are using CodeUnit access. In this sample, I will show how this can be done without using ActiveX and going towards a page.

It’s all about XML

Javascript does not natively have support for strongly typed proxy class generation like C#, VB and Java nor does it have support for interpretation of Soap based webservices like PHP – it is all about XML.

In the end all of the other languages ends up creating a XML document (basically just a string), which is send over the wire to the the Web Service host who then replies back with a XML Document (again just a formatted string).

String manipulation is possible in Javascript and Javascript does have an object called XmlHttpRequest which can communicate with a XML Web Service host – we should be good.

The way NAV 2009 (and SP1) handles Web Services is using SOAP. You can read more about the basics here: http://en.wikipedia.org/wiki/SOAP. This image describes pretty well how your message is put into a body and inserted into an envelope, which the gets send.

image

In the scenario I am implementing here, there are 3 roundtrips to Web Services:

  1. Get the Companies supported on a Web Service Listener
  2. Get a specific Customer
  3. Get all Customers matching a specific filter

Get the Companies supported on a Web Service Listener

In C# we create a reference to the SystemService, which then creates some proxy classes and we just call a method called Companies on these classes.

Underneath that magic, .net will create a XML string that looks much like:

<Soap:Envelope xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
<Soap:Body>
<Companies xmlns="urn:microsoft-dynamics-schemas/nav/system/">
</Companies>

</Soap:Body>
</Soap:Envelope>

As you can see, the only thing I actually send is <Companies /> (with a namespace).

The return value from the Companies method is again

<Soap:Envelope xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
<Soap:Body>
<Companies_Result xmlns="urn:microsoft-dynamics-schemas/nav/system/">
<return_value>CRONUS International Ltd.</return_value>
<return_value>TEST</return_value>
</Companies_Result>
</Soap:Body>
</Soap:Envelope>

In a later post I will show how you can hook into .net and see what XML actually gets sent and what gets received underneath the nice .net surface.

Get a specific Customer

The XML for getting a specific customer looks like:

<Soap:Envelope xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
<Soap:Body>
<Read xmlns="urn:microsoft-dynamics-schemas/page/customer">
<No>10000</No>
</Read>
</Soap:Body>
</Soap:Envelope>

and the return XML from the NAV Customer Page could be:

<Soap:Envelope xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
<Soap:Body>
<Read_Result xmlns="urn:microsoft-dynamics-schemas/page/customer">
<Customer>
<Key>... some huge key ...</Key>
<No>10000</No>
<Name>The Cannon Group PLC</Name>
<Address>192 Market Square</Address>
<Address_2>Address no. 2</Address_2>
... all the other fields ...
</Customer>
</Read_Result>
</Soap:Body>
</Soap:Envelope>

I haven’t included all fields – you probably get the picture.

Get all Customers matching a specific filter

The XML for getting all customers matching a specific filter could be:

<Soap:Envelope xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
<Soap:Body>
<ReadMultiple xmlns="urn:microsoft-dynamics-schemas/page/customer">
<filter><Field>Country_Region_Code</Field><Criteria>GB</Criteria></filter>
<filter><Field>Location_Code</Field><Criteria>RED|BLUE</Criteria></filter>
</ReadMultiple>
</Soap:Body>
</Soap:Envelope>

and the returned XML something like

<Soap:Envelope xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
<Soap:Body>
<ReadMultiple_Result xmlns="urn:microsoft-dynamics-schemas/page/customer">
<ReadMultiple_Result>
<Customer>
... one customer ...
</Customer>
<Customer>
... another customer ...
</Customer>
<Customer>
... a third customer ...
</Customer>
</ReadMultiple_Result>
</ReadMultiple_Result>
</Soap:Body>
</Soap:Envelope>

Enough about the XML – lets see some code

Instead of splitting up the script – I will specify the entire script here and do some explanation beneath.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
    var baseURL = '
http://localhost:7047/DynamicsNAV/WS/';
    var cur;
    var SystemServiceURL = baseURL + 'SystemService';
    var CustomerPageURL;

    var SoapEnvelopeNS = 'http://schemas.xmlsoap.org/soap/envelope/';
    var SystemServiceNS = 'urn:microsoft-dynamics-schemas/nav/system/';
    var CustomerPageNS = 'urn:microsoft-dynamics-schemas/page/customer';

    // Function to Invoke a NAV WebService and return data from a specific Tag in the responseXML
    function InvokeNavWS(URL, method, nameSpace, returnTag, parameters) {
        var result = null;
        try {
            var xmlhttp;
            if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
                xmlhttp = new XMLHttpRequest();
            }
            else {// code for IE6, IE5
                xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
            }

            var request = '<Soap:Envelope xmlns:Soap="' + SoapEnvelopeNS + '">' +
                          '<Soap:Body>' +
                          '<' + method + ' xmlns="' + nameSpace + '">' +
                          parameters +
                          '</' + method + '>' +
                          '</Soap:Body>' +
                          '</Soap:Envelope>';

            // Use Post and non-async
            xmlhttp.open('POST', URL, false);
            xmlhttp.setRequestHeader('Content-type', 'text/xml; charset=utf-8');
            xmlhttp.setRequestHeader('Content-length', request.length);
            xmlhttp.setRequestHeader('SOAPAction', method);

            // Setup event handler when readystate changes
            xmlhttp.onreadystatechange = function() {
                if (xmlhttp.readyState == 4) {
                    if (xmlhttp.status == 200) {
                        xmldoc = xmlhttp.responseXML;
                        xmldoc.setProperty('SelectionLanguage', 'XPath');
                        xmldoc.setProperty('SelectionNamespaces', 'xmlns:tns="' + nameSpace + '"');
                        result = xmldoc.selectNodes('//tns:' + returnTag);
                    }
                }
            }

            // Send request will return when event has fired with readyState 4
            xmlhttp.send(request);
        }
        catch (e) {
        }
        return result;
    }

    // Get the Company list
    function SystemService_Companies() {
        return InvokeNavWS(SystemServiceURL, 'Companies', SystemServiceNS, 'return_value', '');
    }

    function CustomerPage_Read(no) {
        return InvokeNavWS(CustomerPageURL, 'Read', CustomerPageNS, 'Customer',
                           '<No>' + no + '</No>');
    }

    function CustomerPage_ReadMultiple(filters) {
        return InvokeNavWS(CustomerPageURL, 'ReadMultiple', CustomerPageNS, 'Customer', filters);
    }

</script>
</head> 
<body>
<script type="text/javascript">

    var companies = SystemService_Companies();
    document.writeln('Companies:<br>');
    for (var i = 0; i < companies.length; i++) {
        document.writeln(companies[i].text + '<br>');
    }
    cur = companies[0].text;

    CustomerPageURL = baseURL + encodeURIComponent(cur) + '/Page/Customer';
    document.writeln('<br>URL of Customer Page: ' + CustomerPageURL + '<br>');

    var Customer10000 = CustomerPage_Read('10000');
    document.writeln('<br>Name of Customer 10000: ' +
                     Customer10000[0].childNodes[2].firstChild.nodeValue + '<br>');

    document.writeln('<br>Customers in GB served by RED or BLUE warehouse:<br>');
    var Customers = CustomerPage_ReadMultiple(
                    '<filter><Field>Country_Region_Code</Field><Criteria>GB</Criteria></filter>'+
                    '<filter><Field>Location_Code</Field><Criteria>RED|BLUE</Criteria></filter>');
    for (i = 0; i < Customers.length; i++)
        document.writeln(Customers[i].childNodes[2].firstChild.nodeValue + '<br>');

    document.writeln('<br>THE END');

</script>
</body>
</html>

This is the entire Default.htm file.

Most of the “magic” happens inside InvokeNavWS, which really just builds up the XML document based on the method, the namespace and the parameters for the method. This XML document is then sent to the URL and the XML document we get back is read into an XmlDoc and we use XPath to get the return value (the ReturnTag specifies which tags we are interested in).

On top of this method I have created some high level functions so that we can call CustomerPage_Read(‘10000’) and get a Customer back.

Note that Read and ReadMultiple returns a XML NodeList – and the childNodes under the top level nodes are the fields and under the fields you get the field value by saying firstChild.nodeValue.

In this sample I have hardcoded the Name field to be in childNodes[2], this is probably not the way to get the name – but then again, this is only to show how to get a connection up running.

The output of this on my installation looks like:

image

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Logging the XML generated from .net or received from NAV WS

$
0
0

When working with Web Services using languages who doesn’t natively have Web Services support (like Javascript and NAV self) you have to create a SOAP envelope yourself in the correct format.

Of course you can do so by looking at the WSDL, understanding SOAP and using theory – or… – you can create a small C# application, invoke the Web Service you want to and see what XML .net creates for this.

You can also see what XML you get back before .net makes this into classes and feed you something high level.

I only know how to do this with a .net 3.5 application using Service References. I don’t know how to hook into an application using Web References.

I will use the application I created in this post, and basically what we have to do is, to create and plug-in a behavior that prints out the XML.

Endpoint Behaviors

An Endpoint Behavior is a class implementing the IEndpointBehavior interface. This interface consists of 4 methods:

  • public void AddBindingParameters(ServiceEndpoint endpoint,
                System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
  • public void ApplyClientBehavior(ServiceEndpoint endpoint,
                 System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
  • public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
                 System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
  • public void Validate(ServiceEndpoint endpoint)

In my implementation I will leave all empty except for ApplyClientBehavior, where I get access to the ClientRuntime object.

This object has a collection of MessageInspectors – and this is where I want to hook up.

A Client Message Inspector

A Client Message Inspector is a class implementing the IClientMessageInspector interface. This interface consists of 2 methods:

  • public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply,
                 object correlationState)
  • public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request,
                 System.ServiceModel.IClientChannel channel)

and what I want to do in both these methods is to print the contents of the reply/request to the console.

Now there is a special consideration to take here – as soon as you have taken a copy of the message, you have to replace the message with a new one (even if it is a copy of the old one) – else you will get the following error:

image

Actually a very explanatory error message, but kind of weird.

The Code

I created a .cs file and added the following two classes to the file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.Xml;
using System.IO;

namespace testAppWCF
{
    public class MyBehavior : IEndpointBehavior
    {

        #region IEndpointBehavior Members

        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(new MyMessageInspector());
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        {
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }

        #endregion
    }

    public class MyMessageInspector : IClientMessageInspector
    {
        #region IClientMessageInspector Members

        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
            reply = buffer.CreateMessage();
            Message msg = buffer.CreateMessage();
            StringBuilder sb = new StringBuilder();
            XmlWriter xw = XmlWriter.Create(sb);
            msg.WriteBody(xw);
            xw.Close();
            Console.WriteLine("Received:\n{0}", msg.ToString());
            Console.WriteLine("Body:\n{0}", sb.ToString());
        }

        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {
            MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
            request = buffer.CreateMessage();
            Console.WriteLine("Sending:\n{0}", buffer.CreateMessage().ToString());
            return null;
        }

        #endregion
    }
}

Note that the AfterReceiveReply takes special consideration as the actual body comes as a stream and in order to output that I create a XmlWriter and write the body to a string through that one before outputting it to the console.

Adding the behavior to the endpoint

In the main application, we then have to add this to the endpoint, which is done by adding the following line:

systemService.Endpoint.Behaviors.Add(new MyBehavior());

to the code after initialization of the systemService and

customerService.Endpoint.Behaviors.Add(new MyBehavior());

to the code after initialization of the customerService.

F5

When running the application, our console now looks somewhat different.

image

If you look closely you can find Companies: at the top and a list of the companies 3/4’s down.

Everything in between is what is send AND what is received from Web Services. Of course dumping this to the Console isn’t necessarily useful but I assume that you can find ways to dump this to something else if you need.

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Connecting to NAV Web Services from Microsoft Dynamics NAV 2009 SP1

$
0
0

Please read this post to get a brief explanation of the scenario I will implement in Microsoft Dynamics NAV 2009 SP1. Please also read this post in order to understand how Web Services works using pure XML and no fancy objects.

Like Javascript, NAV 2009 SP1 does not natively have support for consuming Web Services. It does however have support for both Client and Server side COM automation and XmlHttp (which is compatible with XmlHttpRequest which we used in the Javascript sample here) is available in Microsoft XML.

Client or Serverside

When running XmlHttp under the RoleTailored Client we have to determine whether we want to run XmlHttp serverside or clientside. My immediate selection was Serverside (why in earth do this Clientside) until I tried it out and found that my server was not allowed to impersonate me against a web services which again needs to impersonate my against the database.

The double hub issue becomes a triple hub issue and now it suddenly becomes clear that XmlHttp in this sample of course needs to run clientside:-)

Compatibility

The sample below will run both in the RoleTailored Client and in the Classic Client.

InvokeNavWS

As in the Javascript example, I will create a function called InvokeNavWS – and in this function I will do the actual Web Service method invocation. In Javascript we setup an event to be called when the send method was done and as you might know, this is not doable on the Roletailored Client.

Fortunately, we are using synchronous web services, meaning that it is actually not necessary to setup this event. We can just check the status when send returns.

xmlhttp.send allows you to send either a string or a XML Document. Having in mind that a string in NAV Classic is max. 1024 characters, I decided to go with a XML Document. In the RoleTailored Client I could have used BigText, but that doesn’t work in Classic.

Creating a XML Document will take slightly more time than building up a large string, but it is the safest way to go. Start by adding an Envelope, a body, a method and then transfer the parameter nodes one by one (there might be smarter ways to do this:-)

The return value is always a nodeList and we only look at the responseXML property of the xmlhttp (which is an XML document).

The Code for InvokeNavWS looks like this:

InvokeNavWS(URL : Text[250];method : Text[20];nameSpace : Text[80];returnTag : Text[20];parameters : Text[1024];VAR nodeList : Automation "'Microsoft XML, v6.0'.IXMLDOMNodeList") result : Boolean
result := FALSE;
// Create XML Document
CREATE(xmldoc,TRUE,TRUE);
// Create SOAP Envelope
soapEnvelope := xmldoc.createElement('Soap:Envelope');
soapEnvelope.setAttribute('xmlns:Soap', '
http://schemas.xmlsoap.org/soap/envelope/');
xmldoc.appendChild(soapEnvelope);
// Create SOAP Body
soapBody := xmldoc.createElement('Soap:Body');
soapEnvelope.appendChild(soapBody);
// Create Method Element
soapMethod := xmldoc.createElement(method);
soapMethod.setAttribute('xmlns', nameSpace);
soapBody.appendChild(soapMethod);
// Transfer parameters by loading them into a XML Document and move them
CREATE(parametersXmlDoc,TRUE,TRUE);
parametersXmlDoc.loadXML('<parameters>'+parameters+'</parameters>');
IF parametersXmlDoc.firstChild.hasChildNodes THEN
BEGIN
  WHILE parametersXmlDoc.firstChild.childNodes.length>0 DO
  BEGIN
    node := parametersXmlDoc.firstChild.firstChild;
    node := parametersXmlDoc.firstChild.removeChild(node);
    soapMethod.appendChild(node);
  END;
END;
// Create XMLHTTP and SEND
CREATE(xmlhttp, TRUE, TRUE);
xmlhttp.open('POST', URL, FALSE);
xmlhttp.setRequestHeader('Content-type', 'text/xml; charset=utf-8');
xmlhttp.setRequestHeader('SOAPAction', method);
xmlhttp.send(xmldoc);
// If status is OK - Get Result XML
IF xmlhttp.status=200 THEN
BEGIN
  xmldoc := xmlhttp.responseXML;
  xmldoc.setProperty('SelectionLanguage','XPath');
  xmldoc.setProperty('SelectionNamespaces','xmlns:tns="'+nameSpace+'"');
  nodeList := xmldoc.selectNodes('//tns:'+returnTag);
  result := TRUE;
END;

and the local variables for InvokeNavWS are

Name              DataType      Subtype                                 Length
xmlhttp           Automation    'Microsoft XML, v6.0'.XMLHTTP   
xmldoc            Automation    'Microsoft XML, v6.0'.DOMDocument   
soapEnvelope      Automation    'Microsoft XML, v6.0'.IXMLDOMElement   
soapBody          Automation    'Microsoft XML, v6.0'.IXMLDOMElement   
soapMethod        Automation    'Microsoft XML, v6.0'.IXMLDOMElement   
node              Automation    'Microsoft XML, v6.0'.IXMLDOMNode   
parametersXmlDoc  Automation    'Microsoft XML, v6.0'.DOMDocument   

As in the Javascript sample I have create a couple of “high” level functions for easier access:

SystemService_Companies(VAR nodeList : Automation "'Microsoft XML, v6.0'.IXMLDOMNodeList") result : Boolean
result := InvokeNavWS(systemServiceURL, 'Companies', SystemServiceNS, 'return_value', '', nodeList);

CustomerPage_Read(No : Text[20];VAR nodeList : Automation "'Microsoft XML, v6.0'.IXMLDOMNodeList") result : Boolean
result := InvokeNavWS(customerPageURL, 'Read', CustomerServiceNS, 'Customer', '<No>'+No+'</No>', nodeList);

CustomerPage_ReadMultiple(filters : Text[1024];VAR nodeList : Automation "'Microsoft XML, v6.0'.IXMLDOMNodeList") result : Boolean
result := InvokeNavWS(customerPageURL, 'ReadMultiple', CustomerServiceNS, 'Customer', filters, nodeList);

The “main” program

OnRun()
baseURL := '
http://localhost:7047/DynamicsNAV/WS/';
systemServiceURL := baseURL + 'SystemService';
SoapEnvelopeNS := '
http://schemas.xmlsoap.org/soap/envelope/';
SystemServiceNS := 'urn:microsoft-dynamics-schemas/nav/system/';
CustomerServiceNS := 'urn:microsoft-dynamics-schemas/page/customer';

CLEAR(nodeList);
IF SystemService_Companies(nodeList) THEN
BEGIN
  DISPLAY('Companies:');
  FOR i:=1 TO nodeList.length DO
  BEGIN
    node := nodeList.item(i-1);
    DISPLAY(node.text);
    IF i=1 THEN cur := node.text;
  END;

  customerPageURL := baseURL + EncodeURIComponent(cur) + '/Page/Customer';
  DISPLAY('URL of Customer Page: '+ customerPageURL);

  IF CustomerPage_Read('10000', nodeList) THEN
  BEGIN
    DISPLAY('Name of Customer 10000: ' + nodeList.item(0).childNodes.item(2).firstChild.text);
  END;

  IF CustomerPage_ReadMultiple('<filter><Field>Country_Region_Code</Field><Criteria>GB</Criteria></filter>'+
'<filter><Field>Location_Code</Field><Criteria>RED|BLUE</Criteria></filter>', nodeList) THEN
  BEGIN
    DISPLAY('Customers in GB served by RED or BLUE warehouse:');
    FOR i:=1 TO nodeList.length DO
    BEGIN
      node := nodeList.item(i-1);
      DISPLAY(node.childNodes.item(2).firstChild.text);
    END;
  END;

  DISPLAY('THE END');

END;

with the following local variables:

Name       DataType      Subtype                                 Length
nodeList   Automation    'Microsoft XML, v6.0'.IXMLDOMNodeList   
node       Automation    'Microsoft XML, v6.0'.IXMLDOMNode   
i          Integer       

As it was the case in the Javascript sample I am using simple xml nodelist code to navigate and display various values. baseURL, cur, SystemServiceURL etc. are all global Text variables used as constants.

DISPLAY points to a function that just does a IF CONFIRM(s) THEN ; to display where we are and running this on the RoleTailored Client will display the following Confirm Dialogs:

imageimageimage

image

image

image

imageimageimageimage

image

Note that the URL of the Customer Page is different from all the other examples. This is because NAV doesn’t have a way of Encoding an URL, so I have to do the company name encoding myself and when I encode a company name, I just encode all characters, that works perfectly:

EncodeURIComponent(uri : Text[80]) encodedUri : Text[240]
// No URI Encoding in NAV - we do it ourself...
HexDigits := '0123456789ABCDEF';
encodedUri := '';
FOR i:=1 TO STRLEN(uri) DO
BEGIN
  b := uri[i];
  encodedUri := encodedUri + '%  ';
  encodedUri[STRLEN(encodedUri)-1] := HexDigits[(b DIV 16)+1];
  encodedUri[STRLEN(encodedUri)] := HexDigits[(b MOD 16)+1];
END;

(Again, there might be smarter ways to do this – I just haven’t found it).

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Connecting to NAV Web Services from Windows Mobile 6

$
0
0

I have created my very first Windows Mobile App!

image

This is running in an Emulator using the Professional SDK.

I also tried to deploy the solution to my physical device (my Smartphone), which also worked:

Smartphone

To be honest, the biggest challenge is to setup everything so that you can get going.

A couple of useful links to get going

Location to download Windows Mobile 6 SDK: http://www.microsoft.com/downloads/details.aspx?FamilyID=06111a3a-a651-4745-88ef-3d48091a390b&DisplayLang=en

The Windows Mobile 6 SDK is not included in Visual Studio 2008, you will have to download and install it.

 

Create your first WM6 App: http://code.msdn.microsoft.com/WM6YourFirstApp

A good video to help you get started.

 

Windows Mobile Development Center: http://msdn.microsoft.com/en-us/windowsmobile/default.aspx

This contains a lot of good links for getting started and how to do things.

 

Security Configuration Manager: C:\Program Files\Windows Mobile 6 SDK\Tools\Security\Security Powertoy

When you have installed the SDK – go to this location and install the security configuration manager to be able to setup your device so that you can deploy your solution and debug it.

 

Note: I did struggle quite a bit to get network access up running on the device and on the emulator, but once I got the emulator setup to have network access (connected to Internet – not Work) and I had access through the firewall to my host machine – then everything worked fine.

The scenario

Please read this post to get a brief explanation of the scenario I will implement on a Windows Mobile Device.

.net 3.5

We will use 3.5 of the compact .net framework to build our application and whether you select Professional (first picture) or Standard (second picture) really doesn’t matter. First thing I do is to create two Web References from my app to the two Web Services i use in my scenario – SystemService (SystemServiceRef) and Customer Page (CustomerPageRef).

These Web References are pretty similar to .net 2.0 Web References from the normal .net framework (look this post). One thing to note is, that you do not have UseDefaultCredentials in the compact framework so you need to specify user and password when connecting to NAV Web Services.

The project type is a Device Application and the code on the form is:

using System;
using System.Windows.Forms;
using SmartDeviceProject4.SystemServiceRef;
using SmartDeviceProject4.CustomerPageRef;
using System.Net;

namespace SmartDeviceProject4
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            string baseURL = ":7047/DynamicsNAV/WS/";'>http://<IP>:7047/DynamicsNAV/WS/";
            NetworkCredential credentials = new NetworkCredential(user, password, domain);

            SystemService systemService = new SystemService();
            systemService.Credentials = credentials;
            systemService.Url = baseURL + "SystemService";
            systemService.PreAuthenticate = true;

            display("Companies:");
            string[] companies = systemService.Companies();
            foreach (string company in companies)
                display(company);
            string cur = companies[0];

            string customerPageURL = baseURL + Uri.EscapeDataString(cur) + "/Page/Customer";
            display("");
            display("URL of Customer Page:");
            display(customerPageURL);

            Customer_Service customerService = new Customer_Service();
            customerService.Credentials = credentials;
            customerService.Url = customerPageURL;
            customerService.PreAuthenticate = true;

            Customer customer10000 = customerService.Read("10000");
            display("");
            display("Name of customer 10000:");
            display(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";

            display("");
            display("Customers 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)
                display(customer.Name);

            display("");
            display("THE END");
        }

        private void display(string s)
        {
            this.textBox1.Text += s + "\r\n";
        }
    }
}

As you can see 99% of the code is similar to the post about C# and Web References (found here). Major differences are that the baseURL of course isn’t localhost (since localhost would be the mobile device itself) and I have to setup credentials in the beginning.

But… It is Very Slow!

Having done this and finally have everything working, you will probably find that Web Services from a mobile device is extremely slow.

Call a service to get Customer 10000 takes approx. 1.5 second – and it is not getting faster if you do it 100 times.

If you set service.preAuthenticate to true – then the time is down to 1.2 second, but still – slower than I would like.

I tried to create a standard .net Web Service on my host computer (asmx web service – just the Hello World sample) and tried to call this method 100 times and in this case, the time was down to around 0.5 second pr. call – still very slow, but more acceptable.

When running some of the other applications a call to a webservice (including authorization) is only around 0.04 seconds on my computer so we are looking at around 30 times slower pr. web service call from a mobile device.

I also tried to make my Hello World application return a 10k string – this didn’t affect the performance at all – and when upping the size of the string to 40k – the time climbed to around 0.7 second pr. call – it seems like the biggest problem is latency (only guessing).

I will do some more investigation on this - including contacting the mobile team in Microsoft to figure out why and how to fix this (if possible).

For now the solution seems to be to create some proxy (with a very limited instruction set = one method for each high level thing the mobile device is capable of doing) running with no authentication and then have the mobile devices communicate with that – maybe using some kind of poor mans authentication – or simply having IP security on the Web Service.

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Connecting to NAV Web Services from Windows Mobile 6.5

$
0
0

It is kind of embarrassing that I write a post about how to connect to NAV Web Services from Windows Mobile 6, when Windows Mobile 6.5 has been out for almost half a year (that’s how much a gadget person I am:-))

I just downloaded the 6.5 SDK from here and tried out the exact same application as I wrote for Windows Mobile 6 in this post and everything seems to work just fine, so please follow the steps in this post to see how to connect from Windows Mobile 6.5 to NAV Web Services.

image

Is it faster?

My tests on 6.5 is only on the emulator – but as far as I can see, it is definitely faster. The 1.5 second from Windows Mobile 6 is now down to 0.9 second and 0.8 second with PreAuthenticate set to true.

I will still do some more investigations on performance from Windows Mobile Web Services.

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV


Connecting to NAV Web Services from Silverlight 3

$
0
0

Please read this post to get a brief explanation of the scenario I will implement in Silverlight. Yes, yes – I know it isn’t a fancy graphical whatever as Silverlight should be, but to be honest – I would rather do something crappy on purpose than trying to do something fancy and everybody would find it crappy anyway:-)

Getting started with Silverlight

http://silverlight.net/getstarted– is your friend. Go to the web site and click this button:

image

Or click the image above directly.

Within a few seconds you will find yourself installing all the free tools you need to start developing Silverlight applications.

On the getstarted web site you will also find videos and walkthroughs on how to develop in Silverlight.

Silverlight is .net and c# so really guys… – how hard can it be?

That was what I thought!

So I just downloaded the Silverlight development platform and started coding and as soon as I tried to connect to NAV Web Services I ran into the showstopper:

image

Meaning that for a Silverlight application to be able to communicate with NAV Web Services – it needs to be deployed in the same location as NAV Web Services – http://localhost:7047– that doesn’t really sound like a good idea.

On MSDN i found this article explaining about this in detail: http://msdn.microsoft.com/en-us/library/cc645032(VS.95).aspx

Silverlight needs permission by the Web Services host to access the Web Service – it kind of seems like overkill due to the fact that our web services are authenticated with Windows Authentication but I guess there are other services where this makes sense.

To make a long story short – if connecting to http://localhost:7047/DynamicsNAV/WS/SystemService– then Silverlight will first try to download http://localhost:7047/clientaccesspolicy.xml and check whether this is OK, but as you can imagine – NAV doesn’t do that:-(

clientaccesspolicy.xml

So if NAV doesn’t support that – how do we get around this obstacle? (of course you know that there is a way – else you wouldn’t be reading this and I wouldn’t be writing it)

The trick is just to create a small windows service that does nothing but host this file. We are lucky that the endpoint of NAV Web Services is http://localhost:7047/DynamicsNAV– and everything underneath that – so I should be able to create a WCF Service hosting just the xml file on http://localhost:7047

NAV Policy Server

I have created a small project called the NAV Policy Server. It is a Windows Service, hosting a WCF Service that will service a “allow all” version of clientaccesspolicy.xml, making Silverlight 3 able to connect to NAV Web Services.

You can read here about how to create a Windows Service (including how to create Setup functionality in the Service). The main program of the Windows Service is here:

using System;
using System.ComponentModel;
using System.ServiceProcess;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Xml;
using System.Reflection;
using System.IO;

namespace NAVPolicyServer
{
    public partial class NAVPolicyService : ServiceBase
    {
        ServiceHost host;

        public NAVPolicyService()
        {
            InitializeComponent();

            string WebServicePort = "7047";
            bool WebServiceSSLEnabled = false;

            // Read configuration file
            XmlDocument doc = new XmlDocument();
            doc.Load(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase), "CustomSettings.config"));
            XmlNode webServicePortNode = doc.SelectSingleNode("/appSettings/add[@key='WebServicePort']");
            WebServicePort = webServicePortNode.Attributes["value"].Value;
            XmlNode webServiceSSLEnabledNode = doc.SelectSingleNode("/appSettings/add[@key='WebServiceSSLEnabled']");
            WebServiceSSLEnabled = webServiceSSLEnabledNode.Attributes["value"].Value.Equals("true", StringComparison.InvariantCultureIgnoreCase);

            // Base listening address
            string BaseURL = (WebServiceSSLEnabled ? Uri.UriSchemeHttps : Uri.UriSchemeHttp) + Uri.SchemeDelimiter + System.Environment.MachineName + ":" + WebServicePort;

            // Initialize host
            this.host = new ServiceHost(new PolicyRetriever(), new Uri(BaseURL));
            this.host.AddServiceEndpoint(typeof(IPolicyRetriever), new WebHttpBinding(false ? WebHttpSecurityMode.Transport : WebHttpSecurityMode.None), "").Behaviors.Add(new WebHttpBehavior());
        }

        protected override void OnStart(string[] args)
        {
            if (host.State != CommunicationState.Opened && host.State != CommunicationState.Opening)
            {
                host.Open();
            }
        }

        protected override void OnStop()
        {
            if (host.State != CommunicationState.Closed && host.State != CommunicationState.Closing)
            {
                host.Close();
            }
        }
    }
}

As you can see, the Service needs to be installed in the Service Tier directory of the Web Service listener you want to enable for Silverlight as it reads the CustomSettings.config file to find the port number and whether or not it uses SSL.

After this it creates a ServiceHost bases on the PolicyRetriever class with a WebHttpBinding endpoint at the base URL, here http://machine:7047. In the endpoint you specify the interface (IPolicyRetriever) this endpoint services and this interface is implemented by the PolicyRetriever class.

The actual code is something I found on Carlos’ blog - http://blogs.msdn.com/carlosfigueira/archive/2008/03/07/enabling-cross-domain-calls-for-silverlight-apps-on-self-hosted-web-services.aspx

The IPolicyRetriever interface is the contract and it looks like:

[ServiceContract]
public interface IPolicyRetriever
{
    [OperationContract, WebGet(UriTemplate = "/clientaccesspolicy.xml")]
    Stream GetSilverlightPolicy();
    [OperationContract, WebGet(UriTemplate = "/crossdomain.xml")]
    Stream GetFlashPolicy();
}

As you can see we host two files – clientaccesspolicy.xml for Silverlight and crossdomain.xml for flash.

The PolicyRetriever class (the Service) itself is implemented as a singleton and looks like:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class PolicyRetriever : IPolicyRetriever
{
    public PolicyRetriever()
    {
    }

    /// <summary>
    /// Create a UTF-8 encoded Stream based on a string
    /// </summary>
    /// <param name="result"></param>
    /// <returns></returns>
    private Stream StringToStream(string result)
    {
        WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml";
        return new MemoryStream(Encoding.UTF8.GetBytes(result));
    }

    /// <summary>
    /// Fetch policy file for Silverlight access
    /// </summary>
    /// <returns>Silverlight policy access xml</returns>
    public Stream GetSilverlightPolicy()
    {
        string result = @"<?xml version=""1.0"" encoding=""utf-8""?>
<access-policy>
    <cross-domain-access>
        <policy>
            <allow-from http-request-headers=""*"">
                <domain uri=""*""/>
            </allow-from>
            <grant-to>
                <resource path=""/"" include-subpaths=""true""/>
            </grant-to>
        </policy>
    </cross-domain-access>
</access-policy>";
        return StringToStream(result);
    }

    /// <summary>
    /// Fetch policy file for Flash access
    /// </summary>
    /// <returns>Flash policy access xml</returns>
    public Stream GetFlashPolicy()
    {
        string result = @"<?xml version=""1.0""?>
<!DOCTYPE cross-domain-policy SYSTEM ""
http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"">
<cross-domain-policy>
    <allow-access-from domain=""*"" />
</cross-domain-policy>";
        return StringToStream(result);
    }
}

The way you make a WCF service a singleton is by specifying an instance of the class to the ServiceHost and set InstanceContextMode to single in the ServiceBehavior Attribute.

That is actually all it takes, installing and starting this service will overcome the connection issue.

The NAVPolicyServer solution can be downloaded here and the compiled .msi (installable) can be downloaded here.

Now… – Connecting to NAV Web Services from Silverlight

Having overcome the connection issue – it is really just to write our Silverlight application.

Create a Silverlight application, insert a StackPanel and a ListBox named output in the .xaml file, add service references and write code.

You will quickly notice, that there is nothing called Add Web Reference – only Add Service Reference – and when you have done do, you will notice that all the functions that you normally invoke are missing…

This is because Silverlight only supports Asynchronous Service access – so much for just creating my standard flow of my app.

Another thing that has changed significantly is what you need to do in order to make a Service Reference work. If you look at my earlier posts with C# and Service References, you can see that I need to setup the binding manually and add endpoints etc. Even if I wanted to do it in a config file (like here), you needed to make a lot of changes to the config file (adding behaviors etc.)

In Silverlight you just add the Service Reference and start partying like:

SystemService_PortClient systemService = new SystemService_PortClient();
systemService.CompaniesAsync();

works right away, no changes needed – THAT’s nice. In my sample I do however build the URL up dynamically, meaning that my construction of the systemService looks like:

SystemService_PortClient systemService = new SystemService_PortClient("SystemService_Port", new EndpointAddress(baseURL + "SystemService"));

Which basically just tells it to read the configuration section and overwrite the endpoint address – still pretty simple.

Async

Whenever you call CompaniesAsync – it returns immediately and after a while the event connected to CompaniesCompleted is triggered. The way I like to do this is to do a inline delegate as an event trigger and just specify my code right there.

My scenario should first list the companies, calculate a customer page URL, read customer 10000 and then read customers with location code BLUE or RED in GB.

public partial class MainPage : UserControl
{
    private string baseURL = "
http://localhost:7047/DynamicsNAV/WS/";
    private string customerPageURL;

    public MainPage()
    {
        InitializeComponent();

        SystemService_PortClient systemService = new SystemService_PortClient("SystemService_Port", new EndpointAddress(baseURL + "SystemService"));
        systemService.CompaniesCompleted += delegate(object sender, CompaniesCompletedEventArgs e)
        {
            display("Companies:");
            for (int i = 0; i < e.Result.Length; i++)
                display(e.Result[i]);
            string cur = e.Result[0];

            this.customerPageURL = baseURL + Uri.EscapeDataString(cur) + "/Page/Customer";
            display(" ");
            display("URL of Customer Page:");
            display(customerPageURL);

            FindCustomer10000();
        };
        systemService.CompaniesAsync();
    }

    void display(string s)
    {
        this.output.Items.Add(s);
    }

}

As you can see, I do not call the FindCustomer10000 before I am done with step 1.

I could have inserted that call after the call to CompaniesAsync – but then the customerPageURL variable would not be initialized when starting to connect to the customer page.

FindCustomer10000 looks like:

private void FindCustomer10000()
{
    Customer_PortClient readCustomerService = new Customer_PortClient("Customer_Port", new EndpointAddress(customerPageURL));
    readCustomerService.ReadCompleted += delegate(object sender, ReadCompletedEventArgs e)
    {
        display(" ");
        display("Name of Customer 10000: " + e.Result.Name);

        FindCustomers();
    };
    readCustomerService.ReadAsync("10000");
}

Again – when we have data and we are done – call FindCustomers, which looks like:

private void FindCustomers()
{
    Customer_PortClient readMultipleCustomerService = new Customer_PortClient("Customer_Port", new EndpointAddress(customerPageURL));
    readMultipleCustomerService.ReadMultipleCompleted += delegate(object sender, ReadMultipleCompletedEventArgs e)
    {
        display(" ");
        display("Customers in GB served by RED or BLUE warehouse:");
        foreach (Customer customer in e.Result)
            display(customer.Name);

        display(" ");
        display("THE END");

    };
    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";
    Customer_Filter[] filters = new Customer_Filter[] { filter1, filter2 };
    readMultipleCustomerService.ReadMultipleAsync(filters, null, 0);
}

If you try to move the call to FindCustomers up after the call to FindCustomer10000 then you will see that it isn’t always determined which of the two methods complete first, meaning that the order of things in the listbox will be “random”.

As you can see, the NAVPolicyServer is really the thing that makes this easy and possible – I will send a mail to my colleague who is the Program Manager for Web Services and ask him to include a way of serving policies from NAV automatically – until then, you will need the policy server (which is free and available right here).

Running the Silverlight application will perform the following output:

image

BTW – the Silverlight application can be downloaded here.

Hopefully this can be used to create some cool visual Silverlight applications:-)

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Connecting to NAV Web Services from VBScript

$
0
0

The Connecting to NAV Web Services series is coming to an end. I think I have covered the majority of platforms from which you would like to connect and use NAV Web Services – some things are easy and some things are a little harder. I did not cover Flash nor did i cover things like the iPhone or iPod Touch, primarily because I don’t think the demand is there. If I have forgotten any platform/language please let me know and if the demand is there I might make something work.

Why VBScript?

Including VBScript makes it possible to do Web Services from scripting like login, shutdown and maintenance scripts. I know that VBScript can also be used from certain browsers but the real idea behind including VBScript here is to enable command line scripts.

Please read this post to get a brief explanation of the scenario I will implement in VBScript.

Please read this post about how to connect to NAV Web Services from Javascript to get an overall explanation about XML Web Services and how to do things without having proxy classes generated etc.

The primary difference between Javascript and VBcript is actually syntax – most of the things are done in a similar way.

The Script file

I created a file called TestWS.vbs and the code to implement the scenario looks like:

function InvokeNavWS(URL, method, nameSpace, returnTag, parameters) 
    Set xmlhttp = CreateObject("MSXML2.XMLHTTP")

    request = "<Soap:Envelope xmlns:Soap="""+SoapEnvelopeNS+"""><Soap:Body><"+method+" xmlns="""+nameSpace+""">"+parameters+"</"+method+"></Soap:Body></Soap:Envelope>"

    ' Use Post and non-async
    xmlhttp.open "POST", URL, false
    xmlhttp.setRequestHeader "Content-type", "text/xml; charset=utf-8"
    xmlhttp.setRequestHeader "Content-length", len(request)
    xmlhttp.setRequestHeader "SOAPAction", method

    ' send request synchronously
    xmlhttp.send request

    ' 200 == OK
    if xmlhttp.status = 200 then
        Set xmldoc = xmlhttp.responseXML
        xmldoc.setProperty "SelectionLanguage", "XPath"
        xmldoc.setProperty "SelectionNamespaces", "xmlns:tns="""+nameSpace+""""
        Set InvokeNavWS = xmldoc.selectNodes("//tns:"+returnTag)
    else
        Set InvokeNavWS = nothing
    end if

end function

' Get the Company list
function SystemService_Companies()
    Set SystemService_Companies = InvokeNavWS(systemServiceURL, "Companies", systemServiceNS, "return_value", "")
end function

' Read one customer
function CustomerPage_Read(no)
    Set CustomerPage_Read = InvokeNavWS(CustomerPageURL, "Read", CustomerPageNS, "Customer", "<No>"+no+"</No>")
end function

' Read Customers
function CustomerPage_ReadMultiple(filters)
    Set CustomerPage_ReadMultiple = InvokeNavWS(CustomerPageURL, "ReadMultiple", CustomerPageNS, "Customer", filters)
end function

sub display(str)
    WScript.echo str
end sub

baseURL = "http://localhost:7047/DynamicsNAV/WS/"
systemServiceURL = baseURL + "SystemService"

soapEnvelopeNS = "http://schemas.xmlsoap.org/soap/envelope/"
systemServiceNS = "urn:microsoft-dynamics-schemas/nav/system/"
customerPageNS = "urn:microsoft-dynamics-schemas/page/customer"

Set Companies = SystemService_Companies()
display "Companies:"
for i = 0 to Companies.length-1
    display Companies(i).text
next
cur = Companies(0).text

customerPageURL = baseURL+escape(cur)+"/Page/Customer"
display ""
display "URL of Customer Page:"
display customerPageURL

Set Customer10000 = CustomerPage_Read("10000")
display ""
display "Name of Customer 10000: "+Customer10000(0).childNodes(2).firstChild.nodeValue

Set Customers = CustomerPage_ReadMultiple("<filter><Field>Country_Region_Code</Field><Criteria>GB</Criteria></filter><filter><Field>Location_Code</Field><Criteria>RED|BLUE</Criteria></filter>")
display ""
display "Customers in GB served by RED or BLUE warehouse:"
for i = 0 to Customers.length-1
    display Customers(i).childNodes(2).firstChild.nodeValue
next

display ""
display "THE END"

The similarity to the Javascript sample is huge (since I am using the same object model), the biggest differences are:

  • The way to encode a URL component in VBScript is by calling escape()– note that escape also exists in Javascript and .net – but there it works differently.
  • Displaying things are done using WScript.echo – this will result in a messagebox if you are using WScript to run the script and a commandline output if you are using CScript (I use CScript)

Running the script

Using the command:

C:\users\freddyk>SCript /nologo testws.vbs

I get the following:

image

and of course you can now to things as redirecting the output to a file and typing or searching in that file:

image

This is something network administrators are experts in doing – I won’t try to compete in any way.

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Web Services Infrastructure and how to Create an Internal Proxy

$
0
0

I am NOT an expert in how to setup a secure network and I do NOT know a lot about firewalls, DMZ setup and all of these things, but I have seen a lot in my 25 years of working with computers and the following (absolutely non-exhaustive) gives a good picture of a common network situation of companies, who wants to interact with customers and partners through Web Applications and/or Web Services.

image

DMZ can be at the customer site or with a hosting center, and yes – there probably is a firewalls between DMZ and Internet.

As you can imagine, this post is NOT about how to setup firewalls and other network infrastructure elements – it is about the software. Let me explain the different machines in this setup (note that there are many different solutions – this is just one possible setup).

  1. This machine is running a Sharepoint server with a time/entry application, document repository linked together with items in the NAV database and KPI’s from NAV using BDC integration from Bugsy’s blog. This machine would typically connect directly to NAV (due to authentication), but can go through the Internal Proxy for some scenarios. There are a lot of posts on my blog and other places on how to connect to NAV Web Services from an internal application like this.
  2. This machine is the internal proxy. It exposes a high level contract for machines in the DMZ on port 8123. The firewall is open for the 2 specific machines in the DMZ on this specific port to this specific machine. This Web Service listener is open for Anonymous access and the machine connects to NAV with a hardcoded user/password.
  3. This is the NAV Service Tier – database could be on the same machine. The Web Service listener listens on port 7047 and uses Windows Authentication.
  4. This machine is a Web Server hosting the company web site and a web shop for selling our products. The authentication between customer and the Web Application is custom and the customer does not have anything to do with the Active Directory in the Intranet.
  5. This machine is a Web Services listener, allowing our top customers to buy our products directly though a module in their own NAV application. The authentication between customer and the Web Service is custom and the customer does not have anything to do with the Active Directory in the Intranet.

In this post – I will concentrate on machine number 2.

My Internal Proxy

I have chosen to create this proxy service as a Windows Service, hosting a WCF Service. The other choice would be to create an asp.net web service application hosted by IIS – which probably is easier to find information about on the internet, but that is just because it has been around for a longer period of time and because the general assumption is that it is complicated.

One advantage you have by making a Windows Service is, that you can have that Windows Service running as a domain account with Access to NAV and this way, you don’t have to have username and password in clear text in your web service host.

Another advantage is that it is lightweight and there really isn’t any reason for having IIS loaded on a computer in the intranet unless it is running your local intranet web site.

I have chosen that the contract I want to implement is to get the Customer name of a specific customer and there really isn’t a lot of code in that.

Writing a WCF service is complicated!

A WCF Service Contract and the service itself is only these few lines of code:

[ServiceContract]
interface IMyInternalProxy
{
    [OperationContract]
    string GetCustomerName(string No);
}

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
class MyInternalProxy : IMyInternalProxy
{
    #region IMyInternalProxy Members

    public string GetCustomerName(string No)
    {
        Customer_Service service = new Customer_Service();
        service.Credentials = CredentialCache.DefaultNetworkCredentials;
        Customer customer = service.Read(No);
        return customer.Name;
    }

    #endregion
}

as you can see I chose to connect to NAV using a Web Reference. I could of course have done this using Service Reference as well as described in this post.

That’s not complicated!

No and actually by hosting the WCF Service in a Windows service you just need to create the ServiceHost and perform the open and close in the proper event handlers like:

public partial class Service1 : ServiceBase
{
    string URL = Uri.UriSchemeHttp + Uri.SchemeDelimiter + Environment.MachineName + ":8123";
    ServiceHost host;

    public Service1()
    {
        InitializeComponent();

        host = new ServiceHost(new MyInternalProxy(), new Uri(URL));
        host.AddServiceEndpoint(typeof(IMyInternalProxy), new BasicHttpBinding(), "");
        ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
        smb.HttpGetEnabled = true;
        smb.HttpGetUrl = new Uri(URL);
        host.Description.Behaviors.Add(smb);
    }

    protected override void OnStart(string[] args)
    {
        if (host.State != CommunicationState.Opened && host.State != CommunicationState.Opening)
            host.Open();
    }

    protected override void OnStop()
    {
        if (host.State != CommunicationState.Closed && host.State != CommunicationState.Closing)
            host.Close();
    }
}

Well that’s not complicated either – but something must be?

No, it’s easy – if it fits, it ships!

(Ok, bad joke, People who hasn’t seen USPS commercials might be a bit confused now:-))

But what about installing, uninstalling etc. etc.

Ok admitted, there is a bit more to writing a Windows Service hosting a WCF service than just writing two classes – but that is the case with IIS hosted Web Services as well. To describe all these things is actually complicated – so instead, I have created a 16 minute video on how to create MyInternalProxy from scratch, install it and create a very small client application for the proxy – and you can download the video here:

http://www.freddy.dk/MyInternalProxy.wmv

I tried uploading to Youtube, but they have a max. of 10 minutes:-(

And…, you can download the solution from the video here.

Hope this is helpful

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

About time to get started again…

$
0
0

Yes, I know. My blog hasn’t been updated for a loooong time and it’s about time to get started again. On November 30th marks it has been 10 month since my last blog entry.

A lot of things has happened since January and some things have stayed the same.

My favorite product is still Microsoft Dynamics NAV and I am still very passionate about Web Services, the RoleTailored Client and everything you can do with NAV – so all of these things haven’t changed.

But why the long wait then???

Well – I decided in January that I was going to relocate back to Denmark.

That doesn’t take 10 months! – you might think and no – it doesn’t, but ever since moving to Redmond in 2007, I have been driving to work on the freeway called 520 and the people who have been there will know that when you pass under the bridge of 40th you can see Mnt. Rainier in the distance and every single time i saw that amazing mountain, I thought to my self – one day… – I will be standing on top of that mountain.

So, I signed up in January to climb Mnt. Rainier on September 9th – 12th 2010 – and so I did. Accompagnied by Per Mogensen from Mergetool.com (NAV Partner), we both made it to the top on the morning of September 12th.

IMG_8585
I am on the left side and Per is on the right side.

Climbing a mountain like Mnt. Rainier is a hard job and requires a lot of training and in January, I was in the worst shape of my life, so basically I used 8 month to get into the best shape of my life and thats where all my spare time went. After that relocating to Denmark, selling stuff, buying stuff, getting settled in – yeah you get it – time goes by.

But, I have a lot of good stuff to share, things that I have demoed at Convergence (both US and EMEA) and at Directions US and EMEA.  Of course things are centered around NAV and new functionality in NAV and with Microsoft Dynamics NAV 2009 R2 coming up – there are a lot of cool things to take advantage of there.

So, stay tuned for a lot of good stuff coming up.

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Connecting to NAV Web Services from the Cloud–Part 1 out of 5

$
0
0

My last post in January 2010 (here) talked about how to create a Proxy for connecting to NAV Web Services from outside the local intranet, by creating a local proxy and exposing this through the firewall to your DMZ.

It still isn’t simple though and there are a lot of tasks to complete in order to make this work:

  1. Define the API which you need to expose to the external application – and create the internal proxy
  2. Decide what authentication method to use (maybe this needs some coding)
  3. Decide what security measures you need to put in place (external proxy, firewall rules, IP level security, etc.)
  4. Setup servers for proxys, modify firewalls etc. etc.

All in all – if you are creating an application, which should be easy to install – all of this really doesn’t make things easier. You need to demand a number of things of the network infrastructure from the customer before you will be able to make things work.

Also, a number of NAV customers might not have the infrastructure at all, which will make things more expensive.

What then?

Imagine if you could create a proxy, which could expose a service in the Cloud in a secure way, without any of the infrastructure problems mentioned above? A Proxy for which you could create an installer and you would just have to give authentication tokens to the people who needs to connect to your service and then communication would flow problem free. You wouldn’t have to change firewalls, you wouldn’t have to setup VPN’s, IP security etc. etc.

That almost sounds too good to be true right?

Windows Azure AppFabric (Servicebus)

As you can imagine, it is possible to expose services in the Cloud problem free – and one mechanism for doing this is using Windows Azure AppFabric – or the Servicebus.

Actually it isn’t rocket science – it is pretty simple and the technology has existed for quite some time.

If you are from the generation, who remembers, that a phone once looked like this:

phone

then you also know that you would have to pick up the phone, turn the lever on the right side a couple of rounds and then some nice lady with a switchboard would ask who you wanted to be connected to.

switchboard

The lady would then physically connect your phone to the phone of the person you want to dial and make the phone ring.

All of this of course runs automatically today, but the basic functionality is the same – you connect to a switchboard which automatically connects you to the person you are calling. The same method is used in network switches and routers of course and if you are running Live Messenger or any other chat program, these programs actually often work in the same way.

You start a program on your computer and now suddenly everybody you know can send instant messages to your computer, which will popup on your screen. This is not because the chat software is polling a service in the cloud 10 times a second, nor is it because the software modifies your firewall to allow other people to connect to you. What happens is, that the software will open up a communication channel to the chat server (everybody is doing that), and then the chat server is working as a router for messages between one computer and the other.

image

The same goes for the Servicebus – it works as a meeting point in the Cloud for WCF services – allowing you to expose a service in the Cloud by opening a communication channel to the Servicebus and then allowing authenticated users to communicate to you through this channel, only difference from the Chat Server is really that you decide on the API of the service in the Cloud.

The hen or the egg

We need to start with either exposing services in the cloud or consuming services in the cloud). In order to see something quickly, I have created a service in the cloud, which is connected to a local installation of Microsoft Dynamics NAV 2009 R2 (could have been SP1) in order to showcase how consuming services hosted on Azure works. This service has a limited API, in fact it is just a cloud version of the Proxy post from back in January (can be found here). This way you can try these samples and actually see that you can call a NAV Service over the Internet.

Note: I will try to keep the service running, but there is absolutely no guarantee that this will be running all the time.

My next blog post will then describe how to actually create this proxy, but for now it is all about consuming.

The service we are testing against only have one method, which returns the Customer Name, given the Customer No. I also exposed a metadata endpoint in the cloud, which you can use to create a service reference to my service, the URL is sb://navdemo.servicebus.windows.net/Proxy1/mex

But wait… – what is sb:// ?

sb is an abbreviation of ServiceBus and you need to install the Windows Azure AppFabric SDK in order to have that available. You can download this SDK here.

Now you might think – does this mean that all clients will have to install this SDK in order to communicate with my service bus hosted service? and the answer is No, you can connect to a service bus hosted service using standard WCF bindings, but in order to do this, you need to expose an HTTPS endpoint. If you want to connect to a SB endpoint, you will need the SDK.

This also means that on the developer machine (on which you create the reference to the metadata endpoint) you will need the SDK installed.

Consuming a Servicebus hosted service from C#

Lets just try the easiest possible way to consume my cloud hosted service and see that it actually works.

Start Visual Studio and create a console application.

Add a Service Reference to the URL: sb://navdemo.servicebus.windows.net/Proxy1/mex (set the Namespace to Proxy1). If this doesn’t work it is probably because you are missing the SDK mentioned above.

Proxy1 Service Reference

Write the following two lines of code:

var client = new Proxy1.ProxyClassClient("NetTcpRelayBinding_IProxyClass");
Console.WriteLine(client.GetCustomerName("10000"));

Run the application and you should get:

Proxy1 output

Kind of fun to think about that every single call you make to this service will run a code snippet in my locally hosted Microsoft Dynamics NAV 2009 – feel free to try – it contains all the customers from the W1 demo database.

In the next post I will go into some more details about how to consume this service without the SDK. I will also explain how to do this from Microsoft Dynamics NAV 2009 R2 and from a Windows Phone 7.

Stay tuned.

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Viewing all 161 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>