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

If you haven’t already read part 4 (and the prior parts) you should do so here, before continuing to read this post.

In this post, I am going to create a small Windows Phone 7 application, which basically will be a phone version of the sidebar gadgets from this post. Continue reading

Connecting to NAV Web Services from the Cloud–part 4 out of 5

If you haven’t already read part 3 you should do so here, before continuing to read this post.

By now you have seen how to create a WCF Service Proxy connected to NAV with an endpoint hosted on the Servicebus (Windows Azure AppFabric). By now, I haven’t written anything about security yet and the Proxy1 which is hosted on the Servicebus is available for everybody to connect to anonymously. Continue reading

Connecting to NAV Web Services from the Cloud–part 3 out of 5

If you haven’t already read part 2 you should do so here, before continuing to read this post.

In part 2 I talked about how to connect to my locally installed NAV Web Service Proxy from anywhere in the world and towards the end, I promised that I would explain how the proxy was build. Problem was, that while writing this post I ran into a bug in the Servicebus – which was really annoying. Continue reading

Connecting to NAV Web Services from the Cloud–part 2 out of 5

If you haven’t already read part 1 you should do so here, before continuing to read this post.

In part 1 I showed how a service reference plus two lines of code:

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

could extract data from my locally installed NAV from anywhere in the world. Continue reading

Web Services Infrastructure and how to Create an Internal Proxy

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

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:

image_2

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

image_4 (1)

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:

image_5

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_2 (1)

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_6

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.

image_2 (2)

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