Creating a bot pt. 8 – Handling Replies

Now we’ve got our bot sending out notifications, we want to be able to handle a reply from the user and add it back into D365 as a new post.

The adaptive card notification lets the user reply via an embedded form. This makes it nice and neat. When the user fills in this form and clicks Reply, three bits of information are sent back to the bot:

  • the ID of the post that the reply is for
  • the domain name of the CDS instance the post is in
  • the text of the reply from the user

This gives us everything we need to create a reply to the post back in CDS so it’s visible to everyone.

These values are available from the turnContext.Activity.Value object using the names defined in the adaptive card:

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    dynamic val = turnContext.Activity.Value;

    var message = val.comment;
    var postId = val.PostId;
    var domainName = val.DomainName;
}

Saving the reply

The first thing we need to do is connect to CDS. We already did this earlier to figure out who to send notifications to. One slight difference here is that we want to create the post as the user sending the reply, not as our application user.

We need to translate the user ID that the bot framework gives us into the one used by CDS. We can’t translate directly between the two, but we can go via the User Principal Name:

var member = await TeamsInfo.GetMemberAsync(turnContext, turnContext.Activity.From.Id, cancellationToken);
var username = member.UserPrincipalName;

using (var svc = new CdsServiceClient(new Uri("https://" + domainName), _config.GetValue<string>("MicrosoftAppId"), _config.GetValue<string>("MicrosoftAppPassword"), true, null))
{
    var qry = new QueryByAttribute("systemuser");
    qry.AddAttributeValue("domainname", username);
    qry.ColumnSet = new ColumnSet("systemuserid");
    var user = svc.RetrieveMultiple(qry).Entities[0];

    svc.CallerId = user.Id;

    svc.Create(new Entity("postreply")
    {
        ["postid"] = new EntityReference("post", postId),
        ["text"] = message
    });
}

Direct Replies

That’s fine when the user replies with the form inside the adaptive card. But what if they just send a new message back to the bot? How do we figure out what they’re replying to?

One way would be to use the dialogs feature of the bot framework. Rather than just sending individual messages to the user for each notification, this would register more of a conversation that’s expecting replies of some format to move the conversation forward.

This seems like a lot of extra management for what we’re trying to achieve here. What we really need is to know what the last message was that we sent to each user, so when they reply we have some context for it.

We’re already saving some user information in Azure Table Storage, so we can just add a couple of extra columns to that table. If we store the ID of the last post that we notified the user of and the domain name of the CDS instance that it came from, we’ll have all the same context information available as if they’d used the adaptive card form.

Wrapping Up

We’ve now got a system that handles everything I set out to do originally:

  • get notifications of new posts & replies from CDS
  • send push notifications out to users in Teams that are connected to each post
  • handle replies to those notifications and push them back to CDS

If someone is interested in the reply, this process will start over again and they’ll get a notification of the reply, and so it goes on. Users can then have a conversation in Teams, where they’re already spending much of their day, but with all the details being logged against the relevant record in D365 for posterity.

Leave a comment

Your e-mail address will not be published. Required fields are marked *

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