Search code examples
.netbotframeworkmicrosoft-teamsmicrosoft-graph-sdksmicrosoft-graph-teams

Sending notification in 1-to-1 chat to users in tenant


I'm developing a notification bot for my organization in Microsoft Teams, using ASP.NET Core, Graph SDK (from Microsoft.Identity.Web.MicrosoftGraphBeta). I'm having hard times to develop a simple notification system to specific users in my tenant. Those users are identified by email address. I already implemented the proactive installation of the app for users and teams, by doing:

The controller that receives a message to send in MS Teams to a specific user

[HttpPost]
[Route("notify-approver")]
public async Task<List<string>> NotifyApprover([FromBody] ApproverMessage approverMessage)
{
            var approvers = await _graphServiceClient.Users.Request().Filter($"mail eq '{approverMessage.Email}'").GetAsync();

            var targetApprover = approvers.CurrentPage.ToList().FirstOrDefault();

            var appInstallation= await _graphServiceClient.InstallIfNotAlreadyForUser(targetApprover.Id, _configuration.GetTeamsAppId());


            // PER IL MOMENTO SEMBRA BUG
            var chatId = await _graphServiceClient.GetChatThreadId(targetApprover.Id, appInstallation.Id)

        }

The installation works fine and returns a UserScopeTeamsAppInstallation. This is the function I built for proactive installing:

public static async Task<UserScopeTeamsAppInstallation> InstallIfNotAlreadyForUser(this IGraphServiceClient graphServiceClient, string userId, string teamsAppId)
        {
            var appsCollectionPage = await graphServiceClient.Users[userId].Teamwork.InstalledApps.Request().Expand("teamsAppDefinition")
                .GetAsync();
            var appInstallation = appsCollectionPage.CurrentPage.ToList()
                .Find(a => a.TeamsAppDefinition.TeamsAppId.Equals(teamsAppId));

            if (appInstallation != null) return appInstallation;
            var userScopeTeamsAppInstallation = new UserScopeTeamsAppInstallation()
            {
                AdditionalData = new Dictionary<string, object>
                {
                    {"teamsApp@odata.bind", $"https://graph.microsoft.com/beta/appCatalogs/teamsApps/{teamsAppId}"}
                }
            };
            var freshInstallation = await graphServiceClient.Users[userId].Teamwork.InstalledApps.Request().AddAsync(userScopeTeamsAppInstallation);
            return freshInstallation;

        }

Until now it's ok, but the bad part is in the trial to send proactively a message; as suggested in the documentation I proceed to get this chatId that should be useful. The funny part is that if I use Graph Client v1.0 I get an internal server error while retrieving this chat object below, then I switched to Beta client and it... kinda works.

public static async Task<string> GetChatThreadId(this IGraphServiceClient graphServiceClient, string userId, string teamsAppIdInstallation)
        {
            // forse ci vuole AppInstallationId anziché il semplice teamsAppId della app

            //var chat = await graphServiceClient.Users[userId].Teamwork.InstalledApps[teamsAppIdInstallation].Chat.Request().GetAsync();

            /*
             * Perché dá internal server error?? Possibile bug della API di graph
             * perché viceversa da graph explorer funziona e restituisce l'id della chat
             *
             * Per adesso sono costretto a salvarmi in un database tutte le conversation references.
             */

            var chat =  graphServiceClient.Users[userId].Teamwork.InstalledApps[teamsAppIdInstallation].Chat.Request().GetAsync();


            return chat.Id.ToString();
            //return "CIAONE";
        }

Chat object obtained when retrieving in GetChatThreadId function From now on I getting more and more confused by the amount of scattered information. What I saw is that usually the thing should be:

  • On the bot callback OnConversationUpdateActivityAsync the ConversationReference should be saved because it is fired when the bot is installed. But then this object is stored in a ConcurrentDictionary and its lifetime is limited to runtime. In my case I should persist it, but I can't find what properties to store and how to recreate the ConversationReference object that in the examples is always stored as it is, but no example with persistence.
  • Given the above problem I can't use on the bot adapter the method ContinueConversationAsync.
  • In any case, if we look at the documentation even if it explicitly say to retrieve this ChatId it performs the operation by using cached ConversationReference objects.

How am I supposed to do such a "simple" thing like sending a simple message to a person while it looks so much confusing?


Solution

  • A simple solution I found was to save the ConversationReference object in the OnTeamsMembersAddedAsync callback when the Bot is installed for a user by serializing it in json and saving into a Sqlite database. For retrieving it i deserialize it and use