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. Continue reading
Monthly Archives: January 2010
Connecting to NAV Web Services from VBScript
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:
and of course you can now do things as redirecting the output to a file and typing or searching in that file:
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
Connecting to NAV Web Services from Silverlight 3
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:
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:
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:
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 Windows Mobile 6.5
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.
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 Windows Mobile 6
I have created my very first Windows Mobile App!
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:
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 Microsoft Dynamics NAV 2009 SP1
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:
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
Logging the XML generated from .net or received from NAV WS
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:
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.
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 Javascript
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.
In the scenario I am implementing here, there are 3 roundtrips to Web Services:
- Get the Companies supported on a Web Service Listener
- Get a specific Customer
- 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″ />
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 = ” +
” +
” +
parameters +
” +
” +
”;
// 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 + ”);
}
function CustomerPage_ReadMultiple(filters) {
return InvokeNavWS(CustomerPageURL, ‘ReadMultiple’, CustomerPageNS, ‘Customer’, filters);
}
</head>
<body>
var companies = SystemService_Companies();
document.writeln(‘Companies:
‘);
for (var i = 0; i document.writeln(companies[i].text + ‘
‘);
}
cur = companies[0].text;
CustomerPageURL = baseURL + encodeURIComponent(cur) + ‘/Page/Customer’;
document.writeln(‘
URL of Customer Page: ‘ + CustomerPageURL + ‘
‘);
var Customer10000 = CustomerPage_Read(‘10000’);
document.writeln(‘
Name of Customer 10000: ‘ +
Customer10000[0].childNodes[2].firstChild.nodeValue + ‘
‘);
document.writeln(‘
Customers in GB served by RED or BLUE warehouse:
‘);
var Customers = CustomerPage_ReadMultiple(
‘Country_Region_CodeGB’+
‘Location_CodeRED|BLUE’);
for (i = 0; i document.writeln(Customers[i].childNodes[2].firstChild.nodeValue + ‘
‘);
document.writeln(‘
THE END’);
</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:
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
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 Visual Basic .net using Web Reference
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