Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Microsoft Bot Framework WebChat: Disable AdaptiveCards submit buttons of previous message

How to disable input/submit button actions in the previous conversation of BotChat - AdaptiveCards in the Microsoft Bot Framework (C#)

like image 630
Prabhakaran Kanniappan Avatar asked Oct 17 '22 14:10

Prabhakaran Kanniappan


1 Answers

I'm imagining you want to display a card to the user that's meant to be used only once, such as a calendar reminder like the one seen in this example.

Bots are mostly meant to have the same kind of access to a channel that a human would, so they can't go back and modify the messages that have already been sent (unless the specific channel allows edits like Slack does). While you can't disable a button in a card that's already part of the conversation history, you can change the way your bot responds to the messages that are generated by that card. What you'll want to do is keep track of whether a button has been clicked and then respond differently when the button is clicked subsequent times.

Here's a basic example of some Dialog code that can respond to messages in three ways. If you type any message and send it to the bot, it will display a card with a button on it. If you click the button, it will say "You did it!" along with the ID of the button you clicked. If you click the same button again, it will say "You already did that!" again attaching the ID.

/// <summary>
/// You'll want a label like this to identify the activity
/// that gets generated in response to your submit button.
/// </summary>
private const string DO_SOMETHING = "DoSomething";

/// <summary>
/// This is passed into context.Wait() in your StartAsync method.
/// </summary>
private async Task MessageReceivedAsync(IDialogContext context,
    IAwaitable<IMessageActivity> result)
{
    var msg = await result;

    if (!string.IsNullOrWhiteSpace(msg.Text))
    {
        // If msg.Text isn't null or white space then that means the user
        // actually typed something, and we're responding to that with a card.
        var reply = context.MakeMessage();
        var attachment = MakeAdaptiveCardAttachment();
        reply.Attachments.Add(attachment);

        await context.PostAsync(reply);
    }
    else
    {
        // If the user didn't type anything then this could be an activity
        // that was generated by your submit button. But we want to make sure
        // it is by checking msg.Value.
        dynamic value = msg.Value;

        try
        {
            // If value doesn't have a type then this will throw a RuntimeBinderException
            if (value != null && value.type == DO_SOMETHING)
            {
                string id = value.id;

                // Check the ID to see if that particular card has been clicked before.
                if (!context.PrivateConversationData.ContainsKey(id))
                {
                    // This is how your bot will keep track of what's been clicked.
                    context.PrivateConversationData.SetValue(id, true);

                    await context.PostAsync("You did it! " + id);
                }
                else
                {
                    await context.PostAsync("You already did that! " + id);
                }
            }
        }
        catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException)
        {
            // Respond to messages that don't have values with a type (or id).
        }
    }

    context.Wait(MessageReceivedAsync);
}

private static Attachment MakeAdaptiveCardAttachment()
{
    var card = new AdaptiveCard();
    // We need to identify this specific card if we want to allow multiple
    // instances of the card to be clicked.
    // A timestamp could work but a GUID will do.
    var cardId = Guid.NewGuid().ToString();

    card.Body.Add(new TextBlock() { Text = cardId });

    card.Actions.Add(new SubmitAction()
    {
        Title = "Do something",
        // The data we put inside this action will persist.
        // I've found setting DataJson to be more reliable than using the Data property.
        // Note that if your WebApiConfig.cs has a CamelCasePropertyNamesContractResolver
        // (which is a default) and you use capitalized (Pascal case) identifiers,
        // they may be converted to camel case and you won't be able to retrieve
        // the data with the same identifiers.
        DataJson = JsonConvert.SerializeObject(new
        {
            // We need a type to differentiate this action from other actions.
            type = DO_SOMETHING,
            // We need an id to differentiate this card from other cards.
            id = cardId,
        }),
    });

    return new Attachment()
    {
        ContentType = AdaptiveCard.ContentType,
        Content = card,
    };
}

Here's what it looks like in Bot Framework Emulator. Note that even after you've clicked one card and can't get the first response from that card, you can still get the first response from the other card.

enter image description here

like image 135
Kyle Delaney Avatar answered Nov 02 '22 13:11

Kyle Delaney