简体   繁体   中英

How to have cancel button in Adaptive cards to be displayed in Web Channel chat bot developed in MS Bot framework SDK V4 in C#?

I have chat Bot developed for Web channel using MS Bot Framework SDK V4 in C# which has multiple waterfall dialog classes each performs specific task. In main root dialog i have set of options displayed Option 1,2,3,4...6. Now when i select an option 5 i get redirected to a new dialog class where

I have an adaptive card that i designed with 3 sets of containers one takes input text through text boxes and second container have some check boxes to be selected and third container contains 2 buttons submit and cancel button. For these buttons i have put data as Cancel = 0 and 1 respectively. In this option 5 dialog i am controlling based on the data cancel-0 or 1 if it is 1 i am doing end dialog and displaying default display options 1,2,3,4...6. Now, i clicked on submit button by entering valid values and the process has been completed successfully as a result the current dialog has ended and again the main set of options are displayed.

Here i did some kind of negative testing where i scrolled up and clicked cancel button which was displayed above. This resulted the first option(option 1 ) displayed in the set of options 1 to 6 selected y default and that option operations got performed automatically even though i selected cancel and not the first option. But this is not happening when i select submit button displayed in adaptive card after scrolling up it is displaying the retry prompt to select any one of the following options where as when i clicked on cancel it is going to 1st option by default.

Please find the dialog related and adaptive card related data below:

{
    "type": "AdaptiveCard",
    "body": [
        {
            "type": "TextBlock",
            "size": "Large",
            "weight": "Bolder",
            "text": "Request For Model/License",
            "horizontalAlignment": "Center",
            "color": "Accent",
            "id": "RequestforModel/License",
            "spacing": "None",
            "wrap": true
        },
        {
            "type": "Container",
            "items": [
                {
                    "type": "TextBlock",
                    "text": "Requester Name* : ",
                    "id": "RequesterNameLabel",
                    "weight": "Bolder",
                    "wrap": true,
                    "spacing": "None"
                },
                {
                    "type": "Input.Text",
                    "placeholder": "Enter Requester Name",
                    "id": "RequesterName",
                    "spacing": "None"
                },
                {
                    "type": "TextBlock",
                    "text": "Requester Email* : ",
                    "id": "RequesterEmailLabel",
                    "weight": "Bolder",
                    "wrap": true,
                    "spacing": "Small"
                },
                {
                    "type": "Input.Text",
                    "placeholder": "Enter Requester Email",
                    "id": "RequesterEmail",
                    "style": "Email",
                    "spacing": "None"
                },
                {
                    "type": "TextBlock",
                    "text": "Customer Name* : ",
                    "id": "CustomerNameLabel",
                    "weight": "Bolder",
                    "wrap": true,
                    "spacing": "Small"
                },
                {
                    "type": "Input.Text",
                    "placeholder": "Enter Customer Name",
                    "id": "CustomerName",
                    "spacing": "None"
                },
                {
                    "type": "TextBlock",
                    "text": "Select Request Type : ",
                    "id": "RequestTypeText",
                    "horizontalAlignment": "Left",
                    "wrap": true,
                    "weight": "Bolder",
                    "size": "Medium",
                    "spacing": "Small"
                },
                {
                    "type": "Input.ChoiceSet",
                    "placeholder": "--Select--",
                    "choices": [
                        {
                            "title": "Both",
                            "value": "Both"
                        },
                        {
                            "title": "1",
                            "value": "1"
                        },
                        {
                            "title": "2",
                            "value": "2"
                        }
                    ],
                    "id": "RequestType",
                    "value": "Both",
                    "spacing": "None"
                }
            ],
            "horizontalAlignment": "Left",
            "style": "default",
            "bleed": true,
            "id": "Requesterdata"
        },
        {
            "type": "Container",
            "items": [
                {
                    "type": "TextBlock",
                    "text": "Select Asset* :",
                    "id": "Assetheader",
                    "horizontalAlignment": "Left",
                    "wrap": true,
                    "weight": "Bolder",
                    "size": "Medium",
                    "spacing": "Small"
                },
                {
                    "type": "Input.ChoiceSet",
                    "placeholder": "",
                    "choices": [
                        {
                            "title": "chekcbox1",
                            "value": "chekcbox1"
                        },
                        {
                            "title": "chekcbox2",
                            "value": "chekcbox2"
                        },
                        {
                            "title": "chekcbox3",
                            "value": "chekcbox3"
                        },
                        {
                            "title": "chekcbox4",
                            "value": "chekcbox4"
                        },
                        {
                            "title": "chekcbox5",
                            "value": "chekcbox5"
                        }
                    ],
                    "isMultiSelect": true,
                    "id": "AssetsList",
                    "wrap": true,
                    "spacing": "None"
                }
            ],
            "id": "Assetdata",
            "style": "default",
            "horizontalAlignment": "Left",
            "bleed": true
        },
        {
            "type": "Container",
            "items": [
                {
                    "type": "ActionSet",
                    "actions": [
                        {
                            "type": "Action.Submit",
                            "title": "Cancel",
                            "id": "CanclBtn",
                            "style": "positive",
                            "data": {
                                "Cancel": 1
                            }
                        },
                        {
                            "type": "Action.Submit",
                            "title": "Submit",
                            "id": "SubmitBtn",
                            "style": "positive",
                            "data": {
                                "Cancel": 0
                            }
                        }
                    ],
                    "id": "Action1",
                    "horizontalAlignment": "Center",
                    "spacing": "Small",
                    "separator": true
                }
            ]
        }
    ],
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.0",
    "id": "ModelLicenseRequestForm",
    "lang": "Eng"
}

Code below of main root dialog:

AddStep(async (stepContext, cancellationToken) =>
{
    return await stepContext.PromptAsync(
        "choicePrompt",
        new PromptOptions
        {
            Prompt = stepContext.Context.Activity.CreateReply("Based on the access privileges assigned to you by your admin, below are the options you can avail. Please click/choose any one from the following: "),
            Choices = new[] { new Choice { Value = "option1" }, new Choice { Value = "option2" }, new Choice { Value = "option3" }, new Choice { Value = "option4" }, new Choice { Value = "option5" }, new Choice { Value = "option6" } }.ToList(),
            RetryPrompt = stepContext.Context.Activity.CreateReply("Sorry, I did not understand that. Please choose any one from the options displayed below: "),
        });
});

AddStep(async (stepContext, cancellationToken) =>
{
    if (response == "option1")
    {
        doing something
    }

    if (response == "option2")
    {
        return await stepContext.BeginDialogAsync(option2.Id, cancellationToken: cancellationToken);
    }

    if (response == "option3")
    {
        return await stepContext.BeginDialogAsync(option3.Id, cancellationToken: cancellationToken);
    }

    if (response == "option4")
    {
        return await stepContext.BeginDialogAsync(option4.Id, cancellationToken: cancellationToken);
    }

    if (response == "option5")
    {
        return await stepContext.BeginDialogAsync(option5.Id, cancellationToken: cancellationToken);
    }

    if (response == "option6")
    {
        return await stepContext.BeginDialogAsync(option6.Id, cancellationToken: cancellationToken);
    }

    return await stepContext.NextAsync();
});

option 5 dialog class code:

AddStep(async (stepContext, cancellationToken) =>
{
    var cardAttachment = CreateAdaptiveCardAttachment("Adaptivecard.json");

    var reply = stepContext.Context.Activity.CreateReply();
    reply.Attachments = new List<Microsoft.Bot.Schema.Attachment>() { cardAttachment };

    await stepContext.Context.SendActivityAsync(reply, cancellationToken);
    var opts = new PromptOptions
    {
        Prompt = new Activity
        {
            Type = ActivityTypes.Message,
            // You can comment this out if you don't want to display any text. Still works.
        }
    };

    // Display a Text Prompt and wait for input
    return await stepContext.PromptAsync(nameof(TextPrompt), opts);
});

AddStep(async (stepContext, cancellationToken) =>
{
    var res = stepContext.Result.ToString();

    dynamic modelrequestdata = JsonConvert.DeserializeObject(res);

    string canceloptionvalidaiton = modelrequestdata.Cancel;
    if (canceloptionvalidaiton == "0")
    {
        // ...perform operation
        return await stepContext.EndDialogAsync();
    }
    else
    {
        return await stepContext.EndDialogAsync();
    }
});

Please note i have purposefully did not provide the whole code for easy understanding and other purposes.

The main idea for me to keep the cancel button is to cancel the current operation so that user can go to main dialog options select any other task to perform

The query is:

  1. How to enable cancel button in adaptive card if my above logic is not correct?
  2. can we have cancel button in adaptive card? or is it a wrong assumption and we cannot have cancel option?

Updated on Nov 8,2019

The below update is for clear and better understanding of my query:

1) When the BOT is launched through Web Channel main root dialog is fired in back end which has all the dialog's and things added to the stack:

Below is the main root dialog class code:

using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Choices;
using Microsoft.Bot.Schema;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace EchoBot.Dialogs
{
    public class MainRootDialog : ComponentDialog
    {
        public MainRootDialog(UserState userState)
            : base("root")
        {
            _userStateAccessor = userState.CreateProperty<JObject>("result");

            AddDialog(DisplayOptionsDialog.Instance);
            AddDialog(Option1.Instance);
            AddDialog(Option2.Instance);
            AddDialog(Option3.Instance);
            AddDialog(Option4.Instance);
            AddDialog(Option5.Instance);
            AddDialog(Option6.Instance);          
            AddDialog(new ChoicePrompt("choicePrompt"));
            InitialDialogId = DisplayOptionsDialog.Id;
        }
    }
}

2) Since the initial dialog is displayedoptionsdialog as a result the following prompt options are displayed in front end to user:

Option1 Option2 Option3 Option4 Option5 Option6

This i achieved through following code which i have written in a class named DisplayOptionsDialog:

using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Choices;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace EchoBot.Dialogs
{
    public class DisplayOptionsDialog : WaterfallDialog
    {
        public DisplayOptionsDialog(string dialogId, IEnumerable<WaterfallStep> steps = null)
            : base(dialogId, steps)
        {
            AddStep(async (stepContext, cancellationToken) =>
            {               

                    return await stepContext.PromptAsync(
                        "choicePrompt",
                        new PromptOptions
                        {
                            Prompt = stepContext.Context.Activity.CreateReply("Below are the options you can avail. Please click/choose any one from the following: "),
                            Choices = new[] { new Choice { Value = "Option1" }, new Choice { Value = "Option2" }, new Choice { Value = "Option3" }, new Choice { Value = "Option4" }, new Choice { Value = "Option5" }, new Choice { Value = "Option6" }}.ToList(),
                            RetryPrompt = stepContext.Context.Activity.CreateReply("Sorry, I did not understand that. Please choose any one from the options displayed below: "),
                        });

            });

            AddStep(async (stepContext, cancellationToken) =>
            {
                var response = (stepContext.Result as FoundChoice)?.Value;

                if (response == "Option1")
                {
                    await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Otpion 1 selected")); //Here there is lot of actual data printing that i am doing but due //to some sensitive inoformation i have kept a simple statment that gets //displayed but in actual code it is just printing back or responding back few //statements which again printing only                                             
                }

                if (response == "Option2")
                {
                    return await stepContext.BeginDialogAsync(Option2.Id, cancellationToken: cancellationToken);
                }

                if (response == "Option3")
                {
                    return await stepContext.BeginDialogAsync(Option3.Id, cancellationToken: cancellationToken);
                }

                if (response == "Option4")
                {
                    return await stepContext.BeginDialogAsync(Option4.Id, cancellationToken: cancellationToken);
                }

                if (response == "Option5")
                {
                    return await stepContext.BeginDialogAsync(Option5.Id, cancellationToken: cancellationToken);
                }

                if (response == "Option6")
                {
                    return await stepContext.BeginDialogAsync(Option6.Id, cancellationToken: cancellationToken);
                }               

                return await stepContext.NextAsync();
            });

            AddStep(async (stepContext, cancellationToken) => 
            {
                return await stepContext.ReplaceDialogAsync(Id);
            });

        }

        public static new string Id => "DisplayOptionsDialog";

        public static DisplayOptionsDialog Instance { get; } = new DisplayOptionsDialog(Id);
    }
}

3) Since the issue w.r.t user selecting Option5 i'll directly go to the option5 dialog class code:

using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace EchoBot.Dialogs
{
    public class Option5Dialog : WaterfallDialog
    {
        public const string cards = @"./ModelAdaptivecard.json";
        public Option5Dialog(string dialogId, IEnumerable<WaterfallStep> steps = null)
           : base(dialogId, steps)
        {

            AddStep(async (stepContext, cancellationToken) =>
           {
               var cardAttachment = CreateAdaptiveCardAttachment(cards);

               var reply = stepContext.Context.Activity.CreateReply();
               reply.Attachments = new List<Microsoft.Bot.Schema.Attachment>() { cardAttachment };

               await stepContext.Context.SendActivityAsync(reply, cancellationToken);
               var opts = new PromptOptions
               {
                   Prompt = new Activity
                   {
                       Type = ActivityTypes.Message,
                       // You can comment this out if you don't want to display any text. Still works.
                   }
               };

               // Display a Text Prompt and wait for input
               return await stepContext.PromptAsync(nameof(TextPrompt), opts);
           });

            AddStep(async (stepContext, cancellationToken) =>
            {               
                var activityTextformat = stepContext.Context.Activity.TextFormat;


                if (activityTextformat == "plain")
                {
                    await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Sorry, i did not understand that please enter proper details in below displayed form and click on submit button for processing your request"));
                    return await stepContext.ReplaceDialogAsync(Id, cancellationToken: cancellationToken);
                }

                else
                {
                    var res = stepContext.Result.ToString();

                    dynamic modelrequestdata = JsonConvert.DeserializeObject(res);

                    string canceloptionvalidaiton = modelrequestdata.Cancel;

                    if (canceloptionvalidaiton == "0")
                    {
                        string ServiceRequesterName = modelrequestdata.RequesterName;
                        string ServiceRequesterEmail = modelrequestdata.RequesterEmail;
                        string ServiceRequestCustomerName = modelrequestdata.CustomerName;
                        string ServiceRequestType = modelrequestdata.RequestType;
                        string ServiceRequestAssetNames = modelrequestdata.AssetsList;


                        //checking wehther data is provided or not
                        if (string.IsNullOrWhiteSpace(ServiceRequesterName) || string.IsNullOrWhiteSpace(ServiceRequesterEmail) || string.IsNullOrWhiteSpace(ServiceRequestCustomerName) || string.IsNullOrWhiteSpace(ServiceRequestAssetNames))
                        {
                            await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Mandatory fields such as Requester name,Requester Email,Cusomter Name or Asset details are not selected are not provided"));

                            return await stepContext.ReplaceDialogAsync(Id, cancellationToken: cancellationToken);
                        }
                        else
                        {
                            await stepContext.Context.SendActivityAsync(MessageFactory.Text("Data recorded successfully"));
                            await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thank You!.Looking forward to see you again."));

                            return await stepContext.EndDialogAsync();
                        }
                    }
                    else
                    {
                        await stepContext.Context.SendActivityAsync(MessageFactory.Text("Looks like you have cancelled the Model/License request"));
                        await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thank You!.Looking forward to see you again."));

                        return await stepContext.EndDialogAsync();
                    }
                }
            });
        }
        public static new string Id => "Option5Dialog";

        public static Option5Dialog Instance { get; } = new Option5Dialog(Id);

        public static Microsoft.Bot.Schema.Attachment CreateAdaptiveCardAttachment(string filePath)
        {
            var adaptiveCardJson = File.ReadAllText(filePath);
            var adaptiveCardAttachment = new Microsoft.Bot.Schema.Attachment()
            {
                ContentType = "application/vnd.microsoft.card.adaptive",
                Content = JsonConvert.DeserializeObject(adaptiveCardJson),
            };
            return adaptiveCardAttachment;
        }
    }
}

Here are things happened or observed during this process of option5 and other things in both positive testing or negative testing:

  1. User provided data in adaptive card displayed as part of Option5 and clicked on submit button user gets the message request id created and etc as shown in above code at same time the dialog is ended and the same default options of Option1 to 6 are displayed as part of defaultDisplayoptions dialog class

  2. Now user scrolls up again and clicks on submit button but as we observe user is in defaultoptions dialog as per code and also as per displayed options

the user is displayed: Sorry, I did not understand that. Please choose any one from the options displayed below:

Option1 Option2 Option3 Option4 Option5 Option6

This is working as needed and as expected so no problems here.

  1. This is the same case how many times ever i click on Submit button

  2. Now i went up and clicked on cancel button this time the control directly went to Displayoptions->Option1 and the statement present in that block got printed

When i debugged i notice the stepcontext in the displayoptions dialog has the text value or choice pre-filled or pre-selected as Option1 without me selecting that option as a result it is printing the statements under it.

Not sure how it is doing it and why it is doing it. So i thought my self may be to include cancel button this way(the way i have done) is wrong may be there is another way and i asked the query how to achieve the cancel button functionality in adaptive card in this post.

However, if what i have done is correct way can you please tell me why the issue is w.r.t only cancel button where when control goes to the DiaplayOptions dialog the option 1 gets pre-selected somehow where as everything works fine w.r.t Submit button(no issues at all in this case any time).

Can you please help me regarding the issue considering my updated information and query?

I have received your code over email and managed to extract the answers to some of my questions.

We know that you must be manipulating the turn context's activity before passing it to the dialog, or else your text prompts could not work with object-based submit actions.

The code I was asking for is in your DialogExtensions.Run class:

Activity activity = dialogContext.Context.Activity;
object rawChannelData = activity.ChannelData;

if (dialogContext.Context.Activity.Value != null && dialogContext.Context.Activity.Text == null)
{
    dialogContext.Context.Activity.Text = turnContext.Activity.Value.ToString();
}

You can see that this is a bad place to put it, since you apparently forgot it was even there. Another reason that's a bad place to put it is that you should be using the builtin DialogExtensions.RunAsync method instead.

What's happening is that you're passing the serialized JSON from the Adaptive Card's submit action into whatever dialog is active. So if the active dialog is a choice prompt, it will try to interpret that serialized JSON as one of the choices. When the cancel button is clicked, that JSON will contain "Cancel": 1 , and the 1 makes the recognizer think you want to go with option 1.

The easiest solution is of course to just rework your Adaptive Card so that it won't contain any numbers, but of course that would be an ad hoc fix that may not work for all your future scenarios.

You haven't actually said what your expected/desired behavior is, but I can think of two main options:

  1. You want the submit actions to be ignored when they're clicked outside of that one prompt
  2. You want the cancel button to cancel any dialog no matter which dialog is active

I can guess by your code that you probably intended for the buttons to only work in that one prompt. Since you're using Web Chat, you might consider a client-side solution where you'd make your own Adaptive Cards renderer that allows submit actions to be disabled after the card is used. I presume that solution is more difficult than you'd like, but there are also ways to have the bot ignore submit actions under specific circumstances. You can have a look at Michael Richardson's Adaptive Card prompt for some ideas, and also vote up my Adaptive Cards community project .

If you want the cancel button to work for any dialog, just make sure you respond to its activity by calling CancelAllDialogsAsync instead of ContinueDialogAsync .

How is "response" generated in your main root dialog

This is the line I was asking for:

var response = (stepContext.Result as FoundChoice)?.Value;

You had inexplicably omitted that line from your "main root dialog," though I notice the line was included when you redundantly pasted that code under the name DisplayOptionsDialog . In the future you will be able to receive better help faster if you don't leave out vital information, or at the very least provide it when asked.

Please refer to my latest blog post for more information about using Adaptive Cards with the Bot Framework.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM