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

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 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

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:

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 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 C# using Service Reference (code version)

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

Code is king

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

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

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

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

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

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

Specifying the endpoint address to the constructor.

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

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

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

The entire application

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

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

Prerequisites

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

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

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

Version and download

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

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

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

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

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

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

How to add a Service Reference

Select Add Service Reference

image_thumb[13]

Type the URL and namespace for the SystemService Service Reference:

image

and for the Customer Page Service Reference:

image

The .config file

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

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

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

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

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

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

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

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

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

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

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

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

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

Confused?

The code

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

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

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

The main code follows.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

image

Authentication

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

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

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

I hope this is helpful.

Good luck

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV