0
votes

I'm using Bot framework V4.3, I have been using adaptive card in waterfall dialog, to get user information, I would want to get values once user clicks submit button and also I would like to go back to previous step if user click back button.

Here is how my adaptive card looks like

Adaptive card

I have tried the solution given by @mdrichardson in Stack Overflow But the adaptive card re-prompts again.

And the below code help us to go back to previous step but how to implement it to back button of adaptive card.

stepContext.ActiveDialog.State["stepIndex"] =(int)stepContext.ActiveDialog.State["stepIndex"] - 2;

Adding adaptive card to dialog. I had even used TextPrompt instead of ChoicePrompt

AddDialog(new ChoicePrompt("AdaptiveCardPrompt") { Style = ListStyle.None });

This is how I'm displaying adaptive card. My adaptive card is in Json format

cardAttachment = CreateAdaptiveCardAttachment();

return await stepContext.PromptAsync("AdaptiveCardPrompt",
    new PromptOptions
    {
        Prompt = (Activity)MessageFactory.Attachment(new Attachment
        {
            ContentType = AdaptiveCard.ContentType,
            Content = cardAttachment.Content
        }),
    }, cancellationToken);

Kindly help me in solving this issue. Thank you in advance

Edit from Botframework Support: Please do not use the code block below. It only works in Emulator. Instead, use:

if (string.IsNullOrWhiteSpace(activity.Text) && activity.Value != null)
{
    activity.Text = JsonConvert.SerializeObject(activity.Value);
}

Edit 1: @mdrichardson Here is how I have setup the dialog call

        public static async Task Run(this Dialog dialog, ITurnContext turnContext,IStatePropertyAccessor<DialogState> accessor, CancellationToken cancellationToken = default(CancellationToken))
        {
            var dialogSet = new DialogSet(accessor);
            dialogSet.Add(dialog);

            var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken);
            // Ensure that message is a postBack (like a submission from Adaptive Cards)
            if (dialogContext.Context.Activity.GetType().GetProperty("ChannelData") != null)
            {
                var channelData = JObject.Parse(dialogContext.Context.Activity.ChannelData.ToString());
                if (channelData.ContainsKey("postBack"))
                {
                    var postbackActivity = dialogContext.Context.Activity;
                    // Convert the user's Adaptive Card input into the input of a Text Prompt
                    // Must be sent as a string
                    postbackActivity.Text = postbackActivity.Value.ToString();
                    await dialogContext.Context.SendActivityAsync(postbackActivity);
                }
            }
            var results = await dialogContext.ContinueDialogAsync(cancellationToken);
            if (results.Status == DialogTurnStatus.Empty)
            {
                await dialogContext.BeginDialogAsync(dialog.Id, null, cancellationToken);
            }
        }

And in OnTurnAsync method

 if (turnContext.Activity.Type == ActivityTypes.Message)
 {
      await Dialog.Run(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
 }

Edit 2 : I modified the code and I was able to go to next waterfall step. But I'm facing another issue here. Next prompt is not getting displayed but I can see it in Log This is how it shows in Emulator

Emulator View

Once user clicks the button control lands in MoreInfoAsync method

 private async Task<DialogTurnResult> MoreInfoAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
 {
            var goback = JObject.Parse(stepContext.Result.ToString());
            stepContext.Values["AdaptiveCardDetails"] = stepContext.Result.ToString();

            if (goback.ContainsKey("goBack"))
            {
                return await stepContext.ReplaceDialogAsync(InitialDialogId);
            }
            // stepContext.ActiveDialog.State["stepIndex"] = (int)stepContext.ActiveDialog.State["stepIndex"] - 2;

            else
                return await stepContext.PromptAsync("MoreInfo", new PromptOptions { Prompt = MessageFactory.Text("Tell Me more.") }, cancellationToken);
 }

I would like to go to initial dialog so I'm using ReplaceDialogAsync.

MoreInfo dialog is not displayed in emulator but its shown in log

Edit 3: Here is the complete code of waterfall steps

          // This array defines how the Waterfall will execute.
                        var waterfallSteps = new WaterfallStep[]
                        {
                            ChoiceAsync,
                            CardAsync,
                            MoreInfoAsync,
                            ConfirmAsync
                        };
                        AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
                        AddDialog(new ChoicePrompt("ChoiceType"));
                        AddDialog(new TextPrompt("AdaptiveCardPrompt"));
                        AddDialog(new TextPrompt("MoreInfo"));
                        InitialDialogId = nameof(WaterfallDialog);

            private async Task<DialogTurnResult> ChoiceAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
            {
                options = new PromptOptions()
                {
                    Prompt = MessageFactory.Text("Select the Choice"),
                    RetryPrompt = MessageFactory.Text("That was not a valid choice."),
                    Choices = GetChoices(),
                    Style = ListStyle.HeroCard
                };
                return await stepContext.PromptAsync("ChoiceType", options, cancellationToken);
            }


            private  async Task<DialogTurnResult> CardAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
            {

                var cardAttachment = new Attachment();
                stepContext.Values["leaveType"] = stepContext.Result.ToString();
                cardAttachment = CreateAdaptiveCardAttachment();
                return await stepContext.PromptAsync("AdaptiveCardPrompt",
                new PromptOptions
                {
                     Prompt = (Activity)MessageFactory.Attachment(new Attachment
                     {
                         ContentType = AdaptiveCard.ContentType,
                         Content = cardAttachment.Content,
                     }),
                }, cancellationToken);
            }

            private async Task<DialogTurnResult> MoreInfoAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
            {
                var goback = JObject.Parse(stepContext.Result.ToString());
                stepContext.Values["AdaptiveCardDetails"] = stepContext.Result.ToString();

                if (goback.ContainsKey("goBack"))
                {
                    return await stepContext.ReplaceDialogAsync(InitialDialogId);
                }
                else return await stepContext.PromptAsync("MoreInfo", new PromptOptions { Prompt = MessageFactory.Text("Tell Me more.") }, cancellationToken);
            }

            private async Task<DialogTurnResult> ConfirmAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
            {
                stepContext.Values["MoreInfo"] = stepContext.Result;
                //As of now I wouldn't perform any task here so I'll end
                return await stepContext.EndDialogAsync();
            }
1
If the card is being displayed again, this likely has more to do with how you have your dialog set up, than how the card is sent. Can you include the code for your dialog? To go back, in the "catch result" step, you'd just need to see if stepContext.Result has your go back value (whatever value you set the card to send when clicking back)mdrichardson
Using ReplaceDialogAsync is a good idea and you're using it correctly. Tough to say why it isn't being shows. Can you either paste all of your code for the MoreInfo dialog or a link to your repo? Can you also describe your new issue a little more? Is it just that the dialog isn't restarting?mdrichardson
I have pasted complete code of MoreInfo dialog if you see emulator snapshot after submitting the submit button the control goes to MoreInfo but in Moreinfo the prompt that im trying to return is not getting displayed in the emulator output screen. But I can see "Tell me more" in emulator log.Why isn't the same getting displayed to user on output. Where have I made a mistakeGags08
It's difficult to tell. MoreInfoAsync looks fine, so the problem is likely elsewhere. Do you have something like AddDialog(new TextPrompt("MoreInfo"));? If you can link to all of your code it would make this much easier to debug.mdrichardson
I wasn't able to replicate this. The problem might be with await dialogContext.Context.SendActivityAsync(postbackActivity); in Run(). Try deleting that, since you don't really need to send that. I've uploaded my code to a github repo so you can take a look and try to find differences. Again, this would be significantly easier to debug with all of your code.mdrichardson

1 Answers

0
votes

Dealing with the Re-Prompt

The issue is with your OnTurnAsync() method:

 if (turnContext.Activity.Type == ActivityTypes.Message)
 {
      await Dialog.Run(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
 }

Every time a user sends a message, it causes a new instance of your dialog to be run. Since Adaptive Card Input gets sent as a PostBack message (which is still a message), it causes the Dialog to run again, re-prompting the user.

If you're going to run dialogs from OnTurnAsync() or OnMessageAsync(), there's a couple of different things you should do, either:

  1. Use if/switch statements. For example, if the message contains "help", run the HelpDialog, or

  2. Start a dialog that saves user responses and skips steps as necessary. You can see an example of this in Core Bot's Booking Dialog. Notice how it's saving the user response in each step with something like bookingDetails.TravelDate = (string)stepContext.Result; and checks to see if it exists in the previous step before prompting with something like if (bookingDetails.TravelDate == null). For yours, you might store something like userProfile.AdaptiveCardDetails or something.

Back Button

To get the back button working, let's say it looks like this in your Adaptive Card:

{
    "type": "Action.Submit",
    "title": "Back",
    "data": {
        "goBack": "true",
    }
},

When the user clicks "Back", the bot will receive an activity with:

enter image description here

Since the user wants to go back and you don't need the data, you could do something like:

var activity = turnContext.Activity;

if (string.IsNullOrWhiteSpace(activity.Text) && activity.Value.GetType().GetProperty("goBack"))
{
    dc.Context.Activity.Text = "Back";
}

and then in your Dialog step:

if (stepContext.Result == "Back")
{
    stepContext.ActiveDialog.State["stepIndex"] = (int)stepContext.ActiveDialog.State["stepIndex"] - 2;
}