How to migrate from Exchange Web Services to Microsoft Graph in ASP.NET Core web application

Photo by Kyrie kim on Unsplash

According to this article, the basic authentication for Exchange Web Services is going to be decommissioned in the second half of the year (2021) and it happens that few years ago, I’ve built a human resources management application that writes and reads data from Exchange calendars and updates some mailbox settings (automatic replies).

Today, I’ve been busy migrating the EWS-related code towards Microsoft Graph and I thought it could be useful to some people to explain how I achieved this and the issues I encountered. Note that this post will only be centered around the feature I had to migrate:

  • Creating calendar events

Note that the code provided below is just some copy/paste of part of the code of the application, so most of the time it won’t be usable “as such” and require minor adaptations to make it more generic.

Note also that you are not forced to use Microsoft Graph, you can keep on using EWS and just update the authentication method. However, the Graph seems to be the preferred way of accessing Microsoft resources.

AAD configuration

First thing you have to do is to create an Azure Active Directory Application that would allow the application to create events in users calendar. I won’t explain all the steps involved in creating such an app as there are already gazillion of articles about that. Instead, I’ll focus on the steps required to use Microsoft Graph.

In the present case, events are created in Exchange by an independent process unrelated to a user. Of course, this process has to know who owns the calendar events are created into, but it’s not the user himself who creates them, so you need to use impersonation with the Graph, meaning that the “client credential flow” of OAuth has to be used. Therefore, you have to go into the “Certificates & secrets” tab to create a new secret :

Create a client secret

Don’t forget to note it as once it is generated, it won’t be possible to retrieve it. Once this is done, some “API permissions” must be defined in order to allow the application to read/write into calendars and update mailbox settings:

API permissions

Note that if events must be created by an application without any user context, you must select the option “Application creation” when selecting the permissions.

Application permission

The application will be ready to be used to access calendars and mailbox settings of users as soon as an administrator has granted the requested permissions.

Install dependencies

Start by installing the following NuGet packages:

Implement MsalHttpClientFactory

If the application you’re working on is not used behind a corporate proxy, you can skip this step. However, if like me, your application passes through a corporate proxy, you need to create a “MsalHttpClientFactory” in order to pass all requests through the proxy. To do so, create a class that inherits from “Microsoft.Identity.Client.IMsalHttpClientFactory”. Here’s mine:

This interfaces requires to implement the method “GetHttpClient” that returns an “HttpClient” object used to make requests. I simply inject the native “IHttpClientFactory” and returns a pre-configured “HttpClient” called “Proxy”. You also have to add this service as a singleton in you DI container:

Implement AuthenticationProvider

The next step consists in implementing the interface “IAuthenticationProvider” to provide a way to authenticate the calls made to Microsoft Graph. This interface requires you to implement the method “AuthenticateRequestAsync” that allows you to update the “HttpRequestMessage” that is going to be sent to Microsoft Graph in order to add authentication to it. Most of the case, the goal of your method will be to add the “Authorization” header to the request with a valid access token:

The constructor creates a new “ConfidentialClientApplication” that is used to retrieve access token from Azure. As you can see, I’m using “WithHttpClientFactory” to specify that HTTP requests must be made using the “HttpClient” using the proxy. As for the other information, such a the “client ID” and the “Tenant Id”, you can easily find them in the Azure portal.

I also implemented a method “GetAccessTokenAsync” that uses the confidential client application created in the constructor to retrieve the OAuth access token. It is important to use this as it leverages the complexity of doing so and also caches and refreshes the access token. Note that I specified the “scope” “offline_access” as it is supposed to allow the access token to be refreshed but I’m not 100% sure that it is required (the SDK probably just fetch a new access token when the previous on is expired).

As usual, don’t forget to register the service in your DI container:

Access the Graph

Now that everything is in place, you need to create the service that will be used by your application to really access the Graph.

Here, the “IGraphClientService” is a custom interface that contains all the signature of the methods provided by our service. I won’t show its code as it depends of the feature you want to provide in your service. Also, remember to register your service as a singleton in your DI container.

Create events

The EWS version of the code used to created events looks like this:

Migrating that code was pretty easy except for one thing… The support of extended properties. The whole event creation and request execution was pretty straightforward but… I spent some time finding the correct syntax for extended properties.

The dates are a bit more strict than with EWS as you have to specify the time zone and the format must be specific.

The creation of simple extended property is really not easy, indeed, it seems that creating such a property requires a very specific syntax for its ID and the SDK does nothing to help you with that. From what I could gather, you can create property of different type but in my case, I only needed string ones so I didn’t bother making the code more generic. The GUID in the property ID actually identifies the “public strings” property set. Finally, you have to put “Name” followed by the name of the property to create. So if you want to create a property called “MyProperty” in the “public strings” property set, the ID of this property should be “String {00020329–0000–0000-c000–000000000046} Name MyProperty”. Not very obvious if you ask me…

Get an event by ID

The code to get an event by ID was the following with EWS:

Once again, the transition was pretty easy except when it came to extended properties:

So basically, I start by crafting a request to get the event based on its ID. However, this request cannot be executed directly as the “Expand” method must be called to load more properties than the standard ones. Here, we want to expand the property “singleValueExtendedProperties” but you cannot just say “OK, I want to fetch all of them”, you have to pass a filter to it and that one must be URL encoded… Basically, the syntax to load two properties (“FirstProperty” and “SecondProperty”) is the following:

singleValueExtendedProperties($filter=id eq ‘String {{00020329–0000–0000-c000–000000000046}} Name FirstProperty or id eq ‘String {{00020329–0000–0000-c000–000000000046}} Name SecondProperty’’)

So here, I simple loop through all the properties to load, create the filter and encode it before passing it to the “Expand” method. Once this is done, the “GetAsync” method can be called to retrieve the event. Once again… Not really straightforward.

Get events between two dates

Fetching all events between two dates using EWS can be done like this:

It gets a bit easier with Graph:

Note that here, I hardcoded the page size (100) and I directly returns the current page because I know that I’ll never have more than 2 or 3 events in the range I use but it would be wise to improve this method to take pages into account.

Delete an event

Deleting an event with EWS is really easy, you just have to get it and then delete it:

Doing so with Graph is even more easier as you don’t even have to fetch it:

However, it’s worth nothing that nothing exists to delete multiple events in one go. Indeed, with EWS, the method “DeleteItems” was available on the connection object, but nothing like this exists with Microsoft Graph. I know that a “batch” endpoint exists but nothing is planned in the SDK to use it so for now, the only option is to loop over your ID list and delete them events one by one.

Recurrences

Creating a recurring event with EWS is pretty easy as well:

And it didn’t get any more complex with Graph:

Except the code related to the extended properties, the rest of the code is pretty simple. Of course, there are other types of recurrences but the properties to define are pretty obvious, so nothing fancy in here.

However, I got annoyed by two things in the code above:

  • Start date and end date of the recurrence must be of type “Microsoft.Graph.Date”. Why didn’t they use a regular “System.DateTime”? It is pretty annoying especially when you retrieve events and want to compare recurrence dates as comparison operators of the “Microsoft.Graph.Date” type are not defined.

Update an event

The code to update an event is pretty straightforward as well, you just need to call the method “UpdateAsync” instead of “AddAsync”:

Note that there is a small caveat: the variable “e” in here cannot be a previously fetched event for which you changed some properties. Indeed, to update an event using Graph you actually need to pass a new “Event” object with only the properties that you want to update. It’s a bit weird at first but the nice thing is that it means that you don’t have to fetch the event in order to update it.

Set automatic replies

Enabling or disabling automatic replies with EWS is really simple:

Doing so with Graph could have been really easy if it was not for a bug in the library.

The issue is that if you just call the “UpdateAsync” method to update the settings of the mailbox as explained in here, the SDK will call the wrong URL as “/mailboxSettings” must be appended it. I also tried to define the “odata.context” additional data but it didn’t work either.

To workaround this issue, you have to build the request the normal way then handling the HTTP request by yourself to append “/mailboxsettings” to the request URL.

Note two things here:

  • The workaround has not been made by me: See this GitHub issue.

Final thoughts

In overall, the migration from EWS to Graph is not really complicated, however, I have mixed feelings about the Graph in itself and the .NET SDK.

Indeed, this technology has been around for more than 5 years now and it still seems pretty “beta” to me. Indeed, the GitHub issue about the wrong URL called to update mailbox settings is 2 years old and is still not fixed. The way of handling extended properties is also very archaic and not straightforward at all. This gives the feeling that the SDK is not taken really seriously by Microsoft and I don’t really understand how it is possible when you think that Microsoft is pushing everyone to use the Graph. Of course, I might be wrong and maybe all the other features work perfectly but I feel that there are a lot of room for improvements in the SDK.

However, it’s worth noting that when a feature is fully supported by the SDK, it works very well and it is pretty fast so maybe there is some hope :-)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store