Web Services Example 6 – Office 365 Inbox

10 Sep

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?

When talking about EWS I actually mean: EWS Managed API. The complete documentation can be found here. And to get started with EWS Managed API you can take a look at this site.

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.

Revised code

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.

AutodiscoverUrl

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.

SynchronizeInbox

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.

BindEmailMessage

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.

6 thoughts on “Web Services Example 6 – Office 365 Inbox

  1. Hi Kauffmann,
    Really grate article as well as the others from this Web Service topics. I am working now on integrating Exchange with NAV 2017 and really spend some time exploring all the possibilities. The example here seems to be the most straight forward one as it is able to handle the Basic authentication as well you get benefits from the managed API objects.

    I am working more on sync To-Do in NAV with Exchange Appointment. Using your example managed to do a Sync process with very small changes. However I am experiencing some difficulties (most because I do no have real .NET experience) . I really stuck on trying to find an Calendar appointment from NAV based on some criteris/filters (ID lets say). I checked lots of .NET examples but do not know how to do in pure C/AL. I will really appreciate and be grateful if you could give me some hints.

    Basically I want to accomplish similar example that is available in .NET samples.

    private static void DeleteAppointment(ExchangeService service)
    {
    // Specify a view that returns a single item.
    ItemView view = new ItemView(1);

    string querystring = “Subject:’Play date at the park'”;

    try
    {
    // Find the first appointment in the calendar with ‘Play date at the park’ set for the subject property.
    // This results in a FindItem operation call to EWS.
    FindItemsResults results = service.FindItems(WellKnownFolderName.Calendar, querystring, view);

    if (results.TotalCount > 0)
    {
    if (results.Items[0] is Appointment)
    {
    Appointment appointment = results.Items[0] as Appointment;

    // Determine whether a meeting request was sent. Meetings must be canceled. Appointments
    // can be deleted. This sample shows how to handle appointments.
    if (!appointment.MeetingRequestWasSent)
    {
    // Delete the appointment from your calendar. This results in a DeleteItem operation call to EWS.
    appointment.Delete(DeleteMode.HardDelete);
    }
    else
    {
    Console.WriteLine(“This is a meeting. Cancel the meeting by using Appointment.CancelMeeting.”);
    }
    }
    }
    else
    {
    Console.WriteLine(“No appointment was found with your search criteria.”);
    }
    }
    catch (Exception ex)
    {
    Console.WriteLine(“Error: ” + ex.Message);
    }
    }

    I tried a lot of things but seems like I am missing some .NET knowledge and I am no able to “translate” maybe the most important part from above
    FindItemsResults results = service.FindItems(WellKnownFolderName.Calendar, querystring, view);
    so that I can put it in some list and deal with it.

    Hope you can give some hints.

    Thanks,
    N. Topalov

  2. A word of thanks on these samples – used it to make calendar creations from NAV 2017 to EWS.
    I do wonder, is this possible to do with AL Code – to access mails.

  3. Great content I must say. Thanks a lot. I need to get it to work with NAV 2018 but the EWS.Wrapper version for NAV 2018 is 11 as against version 9 used here. The TryAutodiscoverUrl function in the Exchange WS Management needs to modified. I’ve spent hours trying to get the ExchangeServiceUrl but to no avail. Can you please help?

    • Honestly, I wouldn’t do this the same way with newer versions. Today I would change to Microsoft Flow, which has a connector for Office 365. That is more reliable and makes the integration with NAV / Business Central way easier.

  4. Very helpful content I must say. However, I’m having issues updating the email following the exact code provided. Every time the pingpong handler starts, I get a full download of the emails instead of updating the table with newer emails. Any suggestion will be very helpful.

    Regards,
    Francis

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.