NAV 2009 R2 has been released for about two weeks now, so it's time to get you up-to-speed on some topics. i'm sure lots of blogs are picking this up, so exciting times are coming :-).
What I have been struggling with lately (and I mean that literally...) is being able to use .Net Interop for something useful .. calling a Web Service. You probably all have seen this in a video that was broadcasted a while ago. I didn't, and that was also the reason why I was struggling, probably ;°). Here is the video: http://www.mibuso.com/dlinfo.asp?FileID=1272. I strongly recommend you to watch it. Some very simple examples and explanation on how to use .NET Interop.
I am a C/AL developer, not a .Net developer. People might think otherwise because of my little project: WaldoNavPad, but no .. with working on this blog, I was reminded in the wealth I was developing (called C/AL, Dynamics NAV, C/SIDE, ...). It's difficult to make the ".NET-switch", and I'm afraid I'm not going to be the only one.. . therefore I'm even more motivated to put up a few blog posts about this great new feature we have in C/AL.. .
What are we gonna do ...
We're going to call a Web Service. You know that publishing a web service has been a peace of cake. Freddy has shown this is in lots of blog posts .. I did a few myself. But what about consuming a Web Service in NAV? The other way around. That hasn't been that easy (just read this article on Freddy's blog, or this article on my blog). Lots of lines of code, nothing straight forward ... .
How is NAV 2009 R2 .NET Interop going to help with this?
Well, we should be able to consume a web service like we do in Visual Studio. On MSDN, there is a walkthrough that helps you with that: http://msdn.microsoft.com/en-us/library/gg502468.aspx. First thing I did was trying to rebuild this scenario. I didn't succeed very well, I must say [:-(]. Probably it was just me, but a few things were left unmentioned, in my opinion.. . I was planning for another scenario, but because of the fact that I had so many troubles in doing the walkthrough, I decided to explain it a bit further so that you ARE able to do this :-). I just changed one thing: I want to read customers, not sales orders.. .
It's a good example on how to connect two NAV databases, companies, ... and let them talk to each other.
STEP 0: Scenario
I published page 22 as a web service in my Dynamics NAV 2009 R2 Database. I've got 2 companies: "First Company" and "Second Company". I want to be able to read customers from company "First Company" in company "Second Company"(sorry for the naming... :-)).
STEP 1: Publish the Web Service
Publishing a page web service is a piece of cake. You all know this. Just add a record in the form (or page) "Web Services":
In my case, this URL represents the web service of company "Second Company": http://localhost:7047/DynamicsNAV/WS/Second Company/Page/Customers
STEP 2: Creating a proxy class
Don't think this step is added to make it difficult on you. On the contrary. We have to create a proxy class to make it easy on you. In stead of exchanging XML, a proxy class makes it possible to use classes to communicate with your web service. You can find more information about a proxy class on the web (Bing it..)
I don't recommend to use the wsdl.exe-tool to create a web service. I did it, and I ended up with a proxy that wasn't useful at all. Therefore, try to follow these steps:
STEP 2: Deploy to Development environment
STEP 3: Write your code
In this step, you basically do what was described in the walkthrough. First of all, you declare your variables:
'System.ServiceModel, Version=126.96.36.199, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.ServiceModel.BasicHttpBinding
'Customer, Version=188.8.131.52, Culture=neutral, PublicKeyToken=null'.Customer.CustomerService.Customers
'System.ServiceModel, Version=184.108.40.206, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.ServiceModel.EndpointAddress
'Customer, Version=220.127.116.11, Culture=neutral, PublicKeyToken=null'.Customer.CustomerService.Customers_PortClient
As you can see, we're using the new datatype "DotNet". I wrote (and copied) thise simple lines of code to read customer 10000:
navBinding := navBinding.BasicHttpBinding;// Set security mode to BasicHttpSecurityMode.TransportCredentialOnlynavBinding.Security.Mode := 4; address := 'http://localhost:7047/DynamicsNAV/WS/Second Company/Page/Customers';// Set client credential type to HttpClientCredentialType.WindowsnavBinding.Security.Transport.ClientCredentialType := 4;CustomerService := CustomerService.Customers_PortClient(navBinding, endpointAddress.EndpointAddress(address));// Set impersonation level to System.Security.Principal.TokenImpersonationLevel.DelegationCustomerService.ClientCredentials.Windows.AllowedImpersonationLevel := 4;// Include the sales order ID to be read.Customer := CustomerService.Read('10000');MESSAGE(Customer.Name);
Probably for the .NET-ers amongst you, this is peace of cake. I'm not one of those, so for me, it was a little bit more difficult to write and understand the code .. Thank you Microsoft for the example :-).
STEP 4: Deploy on RTC Environment
To be able to use this, the dll has to be available at the instance where the business logic is run. Therefore, you have to copy it it to the service tier "Add-Ins" folder, which is located in this map (or similar): C:\Program Files\Microsoft Dynamics NAV\60\Service\Add-ins
Copy the dll to that map as well...
STEP 5: Test your code in the RTC
It's important to know, to be able to run .NET interop code, that you have to be in a client that is using the .NET Framework. Obviously, the RTC is using it and the Classic is not. Therefore, it's not possible to run your .NET Interop Code in the Classic client. You have to find a way to run it in the RTC.
What I did: I added an action on page 9006 (the default Role Center of my testing environment) and just ran the codeunit... .
But what if I wanted to loop my data??
No prob .. . It's actually quite well explained in this blog from Stuart. When calling the "ReadMultiple" method, the web service returns an Array (of the type System.Array). You're able to loop this Array. To test this, I added two more variables to my list:
'mscorlib, Version=18.104.22.168, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Array
'Customer, Version=22.214.171.124, Culture=neutral, PublicKeyToken=null'.Customer.CustomerService.Customers_Filter
Notice you'll have to go to Assembly "mscorlib" to get to the System.Array namespace (sometimes, it's a real struggle to find what you're looking for... ).
Furthermore, I added this to my code:
MyCustomerList := CustomerService.ReadMultiple(CustomerFilter,'',100);FOR i := 0 TO MyCustomerList.Length() -1 DO BEGIN Customer := MyCustomerList.GetValue(i); MESSAGE('Looping Customers: %1 %2', Customer.No, Customer.Name);END;
That's all to it .. simple, isn't it? One small remark: if you want to have lots of records returned, you might have to add an extra line of code to set the Maximum message size. It's the navbinding.maxReceivedMessageSize which I set to 100000 to be able to have all my customers returned... .
When using web services, you're quite forced to go into Visual Studio for a few minutes to create you the proxyclass that you can use ... In fact .. You have to extend you .NET Framework with some webservice-translation-stuff... . Done that, don't forget to deploy: as well as to the classic (to be able to work with it in the dev environment) as to the RTC (server, of course, to be able to use it).
But once you get through these steps, it's REALLY easy to use your web service and go from there.. .
Another thing you should keep in mind .. It should always be executed by the Service Tier! So, remember this blog? where I consumed a NAV 2009 Web Service to print a RTC report in the classic environment? Code could look LOTS simpler in C/SIDE with .NET interop ... BUT ... it would be executed in C/SIDE, so that scenario would not work with .NET Interop ... .
Hi, I have followed your tutorial for dynamics 2015.
It tells that could not load dll when I defined as global variable.
Where should I add .dll ? which folder?
I'm using C# from Visual Studio 2008 to create applications for NAV web services. That's what I also used to create the proxy class. It crossed my mind that it could be a reason for my problem (because you used VS2010), but fortunately I solved it before trying VS2010. :-)
Ok, perfect! :-)
Can I know what environment/language you're using to connect to the webservices?
Problem solved ... I have changed the client authentication to NTLM and it works. :) Thanks a lot for your help!
hm .. then it's getting difficult. Authorization issues on a 3 tier setup are usually due to SPN issues (double SPN's, kerberos cache, ...). I'm not that expert in that.
So hard to solve here, sorry. Only thing I can advice is to put it on Mibuso.
Or may be one tip .. can you test your app on the box where the web service is running?
Yes, 3 tiers but the web services are working since I managed to connect a .NET application and NAV. I also followed Freddy's way (the one you also mentioned in the article) to consume WS and it also works.
Are you running on 3 tiers? Setting up web services on 3 tiers isn't very easy. Lots to take into account :-/
I'm ashamed ... I was so convinced I followed the right steps. I managed to overcome the error, but now it seems that I have an authorization issue:
Microsoft Dynamics NAV
This message is for C/AL programmers: The call to member Read failed: The remote server returned an error: (401) Unauthorized..
The user has SUPER role. I searched for this error on mibuso and on other sites ... nothing to consider. I'm taking advantage of your kindness ... maybe you have an idea about that?!
Just a wild guess .. did you put the dll in the Add-In folder or the Server as well?
The article is very useful. However, I followed your instructions, but I have an error when running the codeunit from RTC:
This message is for C/AL programmers: Unable to create an instance of .Net object: assembly Customer, Version=126.96.36.199, Culture=neutral, PublicKeyToken=null, type Customer.CustomerService.Customers_PortClient.
Do you have any idea about this problem?
I also can't see the update and delete methods for the CustomerService variable. Is there something ele I need to do for that?
First thing: you would be better to post this on Mibuso, as many more readers would be able to give you an answer.
Second: I don't really understand what you try to do. You want to save data from you webservice in a table? please elaborate..
I use your code C/Side from NAV2 database to read pages published from NAV1 database : I copy then data from the tables by web services.
But how to save data in a table? How to use
CustomerService.create (); or navList.CreateInstance ();?
Thanks for your reply,