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:
- How to enable cancel button in adaptive card if my above logic is not correct?
- 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:
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
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.
This is the same case how many times ever i click on Submit button
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?
response
is generated in your main root dialog, and you have not answered that. I asked what "doing something" is because perhaps that code is duplicated somewhere else, so you can't be sure if "option 1" is really the code that's being hit. We can see in the option 5 code that you expect the submit action's result to be serialized into the step context's result, but you still have not provided the code that does that serialization so we can't know why it's triggering option 1. – Kyle Delaney