Categories:

How to get Microsoft Teams messages using Microsoft Graph Delta Query

What is delta query in Microsoft Graph?

Delta query is a new way of querying. It allows us to query the newly created, updated, or deleted entities in resource collection such as Users /Teams /Groups/ Mail Messages with the help of state token.

Why delta query in Microsoft Graph?

If we need to track the latest update of the data whenever it is changed, usually we will fetch all the data then loop and compare it with local data store.

But with Delta Query, in case of change notifications we can get only the updated/added/deleted records which reduces process time to keep the data in sync with your local data store. We just need to have the updated State tokens stored in our local storage.

For instance, Delta query is very useful to get and sync the Teams messages in case if you need to back up messages.

Using delta query to Get Microsoft Teams messages:

Even though we use delta query, it’s not an automatic trigger. So with the respective state tokens you need to call the endpoints again in specific time period, something like timer.

We will use for loop in the below example.

For this demo we are going to use console application and get the message from a channel.

Step 1:

  1. Create a console application in .net Core.
  2. Install packages Microsoft.Graph.Beta and Microsoft.Identity.Client.

Step 2:

  1. Using Microsoft.Identity.Client we are going to create GraphServiceClient.
  2. We are going to use delegated account and create authProvider. Below is  the code sample for creating GraphServiceClient.

public GraphServiceClient GetUsernamePasswordProviderClientNew()

{

string token = GetToken();

       GraphServiceClient graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(async requestMessage =>

{

    requestMessage.Headers.Authorization =

                        new AuthenticationHeaderValue(“Bearer”, token);

       }));

       return graphServiceClient;

}

public string GetToken()

{

try

       {

              string tokenEndPoint = string.Format(“https://login.microsoftonline.com/{0}/oauth2/v2.0/token”, tenantID);

var data = new FormUrlEncodedContent(new[]

       {

              new KeyValuePair<string, string>(“client_id”, clientId),

              new KeyValuePair<string, string>(“scope”, “openid”),

              new KeyValuePair<string, string>(“client_secret”, clientSecret),

              new KeyValuePair<string, string>(“grant_type”, “password”),

              new KeyValuePair<string, string>(“username”, emailForDelecatedAccount),

              new KeyValuePair<string, string>(“password”, asswordForDelecatedAccount)

});

       TokenResponse response = getToken(tokenEndPoint, data);

return response.access_token;

}

catch (Exception)

{

throw;

}

}

private static TokenResponse getToken(string uri, FormUrlEncodedContent data)

{

try

       {

              using (HttpClient httpClient = new HttpClient())

              {

                    HttpResponseMessage response = httpClient.PostAsync(uri, data).Result;

                    response.EnsureSuccessStatusCode();

                    string content = response.Content.ReadAsStringAsync().Result;

                    return JsonConvert.DeserializeObject<TokenResponse>(content);

              }

       }

       catch (Exception)

       {

              throw;

}

}

Step 3:

  1. In Main method we are going to call a method that sync’s chat (GetChannelChatdelta).
A screenshot of a social media post

Description automatically generated
  • For understanding DeltaQuery better, I have hardcoded ChannelId and GroupId. So we will get the message sync from one channel alone. In your code you can make record of ChannelId, GroupId and DeltaToken in SQL table and use to query messages by looping throw each record in table.
  • DeltaQuery is a three-step process to get the change notifications.A GET request with the delta function returns either:
    • A nextLink (that contains a URL with a delta function call and a skipToken), or A deltaLink (that contains a URL with a delta function call and deltaToken).
    • If a nextLink URL is returned, there is additional pages of data to be retrieved in the session. The application continues making requests using the nextLink URL to retrieve all pages of data until a deltaLink URL is returned in the response.
    • b. If a deltaLink URL is returned, then there is no more data about the existing state of the resource. For future requests, the application uses the deltaLink URL to get the resource data.

Note: Delta will only return messages within the last eight months.

Variable token is used to store State tokens (nextLink or deltaLink).

Process 1:

When Initial i==0; we don’t have State tokens. We are going to give a get request with Teams Id and Channel Id.

From the response we will get the nextLink or deltaLink token. we are formatting State token to store in token our application based on the state token.

From all Message we are going to take the messages and print in the console window.

Process 2:

When i==1…; If we get the nextLink, we pass skiptoken in QueryOption to get the resource data.

If there are more data to return, we will get the next skip token and we repeat the process 2.

If there is no more data to return, then we get the deltaLink in the response.

Process 3:

When i>1…; we are going to pass deltatoken in QueryOption. And we are going to get the deltaLink in the response.

For getting reply message. We need to use get replies. Change notifications are not applicable for reply messages.

ReplyMessage also can have nextLink. So, we need to use same methodology to get reply. I have attached complete code of the GetChannelChatdelta Method below.

public static void GetChannelChatdelta(GraphApiAuthenticationService _graphApiAuthenticationService)

{

string teamsId = “76eea5ad-e2fc-454e-9b61-3e073ec9af09”;

string channelId = “19:fc6215d036d0411d862216b4d42af952@thread.tacv2”;

string token = string.Empty;

       string replyToken = string.Empty;

       var storeReplyMessages = new List<ChatMessage>();

GraphServiceClient graphClient = _graphApiAuthenticationService.GetUsernamePasswordProviderClientNew();

       graphClient.BaseUrl = “https://graph.microsoft.com/beta”;

       for (int i = 0; ; ++i)

       {

              IChatMessageDeltaCollectionPage allMessages = null;

              if (string.IsNullOrEmpty(token))

              {

                    allMessages = graphClient.Teams[teamsId].Channels[channelId].Messages.Delta().Request().GetAsync().Result;

              }

              else

              {

                    List<QueryOption> queryOptions = null;

                    if (token.StartsWith(“$skiptoken=”))

                    {

                        var skiptoken = token.Substring(token.IndexOf(“=”) + 1, token.Length – token.IndexOf(“=”) – 1);

                        queryOptions = new List<QueryOption>(){ new QueryOption(“$skiptoken”, skiptoken) };

                    }

                    else

                    {

                        var deltatoken = token.Substring(token.IndexOf(“=”) + 1, token.Length – token.IndexOf(“=”) – 1);

                        queryOptions = new List<QueryOption>(){new QueryOption(“$deltatoken”, deltatoken) };

                    }

                    allMessages = graphClient.Teams[teamsId].Channels[channelId].Messages.Delta().Request(queryOptions).GetAsync().Result;

                }

                if (allMessages.Count == 0) continue;

                if (allMessages.NextPageRequest != null && allMessages.NextPageRequest.QueryOptions != null)

                {

                    foreach (QueryOption queryOption in allMessages.NextPageRequest.QueryOptions)

                    {

                        if (queryOption.Name == “$skiptoken”)

                            token = string.Format(“$skiptoken={0}”, queryOption.Value);

                    }

                }

                if (allMessages.AdditionalData.TryGetValue(“@odata.deltaLink”, out object deltaLink))

                {

                    string skiptoken = Convert.ToString(deltaLink);

                    skiptoken = skiptoken.Substring(skiptoken.IndexOf(“=”) + 1, skiptoken.Length – skiptoken.IndexOf(“=”) – 1);

                    token = string.Format(“$deltatoken={0}”, skiptoken);

                }

                foreach (ChatMessage message in allMessages)

                {

                    if (message.DeletedDateTime.HasValue)

                    {

                        //Message delete

                        string text = string.Format(“New Message from:{0}:{2} \n Message ID:{1}”, message.From.User.DisplayName, message.Id, message.DeletedDateTime);

                        Console.WriteLine(text);

                        Console.WriteLine(“——–“);

                    }

                    else {

                        if (!message.LastModifiedDateTime.HasValue)

                        {

                            // new message

                            string text = string.Format(“New Message from:{0}:{2} \n Message:{1}”, message.From.User.DisplayName, message.Body.Content, message.CreatedDateTime);

                            Console.WriteLine(text);

                            Console.WriteLine(“——–“);

                        }

                        else

                        {

                            string text = string.Format(“Message Edited by:{0}:{2} \n Message:{1}”, message.From.User.DisplayName, message.Body.Content, message.LastModifiedDateTime);

                            Console.WriteLine(text);

                            Console.WriteLine(“——–“);

                        }

                        IChatMessageRepliesCollectionPage allReplayMessages = null;

                        if (string.IsNullOrEmpty(replyToken))

                        {

                            allReplayMessages = graphClient.Teams[teamsId].Channels[channelId].Messages[message.Id].Replies.Request().GetAsync().Result;

                        }

                        else

                        {

                            List<QueryOption> queryOptions = null;

                            if (token.StartsWith(“$skiptoken=”))

                            {

                                var skiptoken = token.Substring(token.IndexOf(“=”) + 1, token.Length – token.IndexOf(“=”) – 1);

                                queryOptions = new List<QueryOption>() { new QueryOption(“$skiptoken”, skiptoken) };

                            }

                            allReplayMessages = graphClient.Teams[teamsId].Channels[channelId].Messages[message.Id].Replies.Request(queryOptions).GetAsync().Result;

                        }                       

                        var newReplyMessages = new List<ChatMessage>();

                        foreach (ChatMessage replyMessage in allReplayMessages)

                        {

                            // use query to find the message id already exists in your DB as of now we dont have change notification for reply messages.

                            if (!storeReplyMessages.Any(x => x.Id == replyMessage.Id))

                            {

                                storeReplyMessages.Add(replyMessage);

                                newReplyMessages.Add(replyMessage);

                            }

                        }

                        if (allReplayMessages.NextPageRequest != null && allReplayMessages.NextPageRequest.QueryOptions != null)

                        {

                            foreach (QueryOption queryOption in allReplayMessages.NextPageRequest.QueryOptions)

                            {

                                if (queryOption.Name == “$skiptoken”)

                                    replyToken = string.Format(“$skiptoken={0}”, queryOption.Value);

                            }

                        }

                        if (newReplyMessages.Count == 0) continue;                  

                        foreach (ChatMessage replyMessage in newReplyMessages)

                        {

                            if (replyMessage.DeletedDateTime.HasValue)

                            {

                                storeReplyMessages.Remove(replyMessage);

                            }

                            else if (!replyMessage.LastModifiedDateTime.HasValue)

                            {

                                // new reply message

                                string replyText = string.Format(“New reply Message from:{0} \n Message:{1}”, replyMessage.From.User.DisplayName, replyMessage.Body.Content);

                                Console.WriteLine(replyText);

                                Console.WriteLine(“——–“);

                            }

                            else

                            {

                                string replyText = string.Format(“Reply Message Edited by:{0} \n Message:{1}”, replyMessage.From.User.DisplayName, replyMessage.Body.Content);

                                Console.WriteLine(replyText);

                                Console.WriteLine(“——–“);

                            }

                        }

                    }

                }

                Thread.Sleep(30 * 1000);

                if (i == 20)

                {

                    break;

                }

            }

        }

Finally, here is the output. We will get the Message and replays in Console. The sample demo code runs every 30 sec.

Conclusion: I hope this article helps you to get understand the new way in syncing the data from Microsoft Graph and keep your data synced using Delta Query. If you have any questions/issues about this article, please let me know in comments.

Sharing is Caring!

Leave a Reply

Your email address will not be published. Required fields are marked *