Better late than never… When writing the last post about the web services examples I couldn’t imagine that the next one would take so long. However, here it is! And the code has been completely revised.
This example is about integrating with Office 365 using web services. And I assume, without having tested it, that it will also work with on-premise Exchange server. Just one warning, before we look at the example. It is not a complete example of everything you could do with Exchange Web Services. But when you explore the possibilities, you will be amazed and most likely lose a lot of time trying out everything that is now at your fingertips.
For those readers who don’t want to wait: the complete code can be downloaded from GitHub: https://github.com/ajkauffmann/DynamicsNAV
Exchange Web Services
With Exchange Web Services (EWS) you can connect and communicate with the Exchange server. It is a perfect mechanism to receive data from e.g. Office 365 (which is actually Exchange Online). The example is about displaying incoming mail messages in an inbox window, including access to details like sender, subject and body. And what’s more, it downloads the attachments. Do I need to explain what possibilities you get with that? Think about the Incoming Documents in NAV 2016. You can get documents in it in several ways, but it is lacking functionality to read emails directly from an inbox. Now this so-called email harvesting becomes possible!
And to give you some other ideas: it is also possible to deal with calendars, contacts and tasks. That’s opening doors, right?
EWS works with an external assembly. This assembly provides the classes that we need to communicate with Exchange and classes for all types of data that can be worked with. The EWS Managed API assembly can be downloaded as nuget package: https://www.nuget.org/packages/Microsoft.Exchange.WebServices. And if you are interested in the source code you should look here: https://github.com/officedev/ews-managed-api.
However, to use EWS from Dynamics NAV you don’t need to download any file at all. The assembly is already included with Dynamics NAV. In the Add-ins folder you will find a NavEwsWrapper folder that contains exactly the same assembly. It’s not the complete nuget package, that one contains an extra dll Microsoft.Exchange.WebServices.Auth.dll. But we don’t need that one, so we can just stick to the assembly that is delivered with Dynamics NAV.
How does the example work?
Everything can be managed from Page 60020 Exchange Inbox.
The first thing you need to do is to open the Setup page.
Type in the user name and password of the Exchange inbox that you want to monitor. Then click the Validate Settings action. That will validate the credentials and try to automatically find the correct URL. For Office 365 it should look like https://outlook.office365.com/EWS/Exchange.asmx. Close the page when ready.
That’s all! Now you can synchronize the inbox. You can either manually synchronize it or do an automatic synchronize every 3 seconds (or whatever interval you set in the code).
After synchronization the status will be saved in the table (field Sync State in table 60020 EWS Setup). This state will be used with the next synchronization to get only the new or changed items instead of a complete list of all existing items. To clear this state click on the Clear Synchronization State action in the Inbox page. That will force a full download of all items during the next synchronization.
Downloaded mail items are stored in table 60021 EWS Mail Item. The primary key of this table is the field ItemId. Every single item on Exchange Server gets a unique id. By reusing this id as the primary key we make sure to avoid duplicate items.
Attachments that are included with the email are stored in table 60022 EWS Attachment. Only real file attachments will be stored. Other attachments like inline images are ignored. The attachments are displayed in a factbox and can be opened by from there.
The very first version of this example included a custom assembly. I needed that one because the .Net interoperability with Dynamics NAV didn’t allow me to work with certain classes from the EWS assembly. The main problem was that the usage of generics with constraints and the usage of fields (which is not supported by NAV) instead of properties or enums.
But the code has now been revised and works without any custom assembly, other than the assemblies that are installed by default with Dynamics NAV. That means that I was able to get around fields and restricted generics.
I’m not going to discuss every single line of code. Just a couple of highlights. For simplicity reasons all relevant code is put in one Codeunit 60020 Exchange WS Management.
This function uses a TryFunction TryAutodiscoverUrl to find the correct URL and catch any error that might occur. This is basically what the TryFunction was designed for. (I would still love to see a real TRY … CATCH in Dynamics NAV, but that is different story…)
In the TryAutodiscoverUrl we use the assembly Microsoft.Dynamics.Nav.EwsWrapper.dll to find the correct URL. This assembly is in the same folder as the EWS assembly and is (as far as I could see) a wrapper to get around some really incompatible features of .Net like callbacks. If you compare it with Codeunit 5321, where the same assembly is being used, you can see that I use it in a different way. However, the result is exactly the same.
This function was the main reason for creating a custom assembly. The ExchangeService class has a method SyncFolderItems. This functions returns a ChangeCollection which is a generic with a constraint on TChange. The actual returned collection is a collection of ItemChange objects. So, what’s the problem here? Dynamics NAV treats generics always as Object. So instead of accepting a ChangeCollection<ItemChange> as the result type it expects a ChangeCollection<Object>. Which is not allowed by the ChangeCollection class since it must be a ChangeCollection<TChange> where TChange can be either FolderChange or ItemChange. You will get this error message when you call the SyncFolderItems on the ExchangeService class:
So how could I get around this by pure C/AL code? The first steps was to look at the ChangeCollection class. This class implements the IEnumerable interface. The only thing that I want to do with the ChangeCollection class is to loop through its items. By using the FOREACH method which is possible on any type that implements the IEnumerable interface. So I created the ChangeCollection of type System.Collections.IEnumerable instead of the actual type.
However, that was just the first step. Now I could loop through the collection by using FOREACH ItemChange in ChangeCollection DO… But C/AL still failed on the method call itself. The only possible solution was to switch to Reflection. With Type.InvokeMember I was able to call the method SyncFolderItems and store the result in ChangeCollection.
Now the loop was almost ready. The final part was to check the ChangeType of the ItemChange. I decided to only implement the Create. But as you can see it is also possible to handle Update, Delete and ReadFlagChange.
This function actually reads the message itself. The ItemChange in the ChangeCollection has a property Item which refers to the actual mail item. However, this item doesn’t have all related properties. For example, it does not contain the body or attachment. With the EmailMessage.Bind method you can specify which additional properties you want to download. Otherwise you will get the first-class properties.
The EmailMessage.Bind method accepts a PropertySet parameter that contains the set of properties that you want to download. The properties are defined by the ItemSchema class. If we want to download the body in text format, we need to specify ItemSchema.TextBody. Unfortunately, all properties are defined as fields. Which is not supported by Dynamics NAV. This problem has caused me some headaches… Eventually, I found at that it is possible to get the field value by using Reflection. Yes, again Reflection was to the rescue!
The solution was to use the Type.GetField method. This method returns a FieldInfo object. And this type has a GetValue method which returns the actual value of the field. Because the fields of the ItemSchema class are static, it is not needed to pass an instantiated object. So I have declared a variable with name null of type System.Object and used that as parameter.
And so I managed to avoid a custom assembly and squeeze the total solution into pure C/AL. Which is, in my opinion, a good option. Of course the code is not as optimal as it would be in C#, but it saves you the hassle of maintaining Visual Studio projects and deploying custom assemblies.
The InsertEmailMessage function is fairly straight-forward and should be easy to understand.
I hope you enjoyed the ride and get a lot of inspiration from this example. Both in possible solutions and in the way how to push .Net Interoperability with C/AL to a next level.