I am new to Bot Framework v4 and am trying to write a bot that has several commands. Let's say they are help, orders, and details.
help - retrieves a message containing the commands available.
orders - retrieves a card with a table of data.
details - begins a waterfall dialog retrieving that same table of data, asks for the order #, and then retrieves its details.
The issue I'm facing is that the code in OnMessageActivityAsync runs on the second turn of the dialog. So, the user sends details command and the dialog begins, retuning the list of orders. Then the user sends an order number, but OnMessageActivityAsync runs and the switch statement hits the code in the default block.
How would I solve this? I tried to figure out a way to check if a dialog is in progress, then run the _dialog.RunAsync(... method if it is.
I've looked around for answers but haven't found anything that works. I've followed Microsoft guides to make this bot, like this guide on dialogs. I also found bot samples, which includes a bot with multiple commands, and a bot with a dialog. However, I have not found an effective way to unify them.
My code looks something like so...
TroyBot.cs
public class TroyBot<T> : ActivityHandler where T : Dialog { private readonly ConversationState _conversationState; private readonly UserState _userState; private readonly T _dialog; public TroyBot(ConversationState conversationState, UserState userState, T dialog) { _conversationState = conversationState; _userState = userState; _dialog = dialog; } protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken) { foreach (var member in membersAdded) { if (member.Id != turnContext.Activity.Recipient.Id) { await turnContext.SendActivityAsync(MessageFactory.Text( "Hi! I'm Troy. I will help you track and manage orders." + "\n\n" + "Say **`help`** to see what I can do." ), cancellationToken); } } } protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken) { var command = turnContext.Activity.Text.Trim().ToLower(); switch (command) { case "orders": var orders = await GetOrders(); Attachment ordersAttachment = GetOrdersAttachment(orders); await turnContext.SendActivityAsync(MessageFactory.Attachment(ordersAttachment), cancellationToken); break; case "help": var helpText = GetHelpText(); await turnContext.SendActivityAsync(MessageFactory.Text(helpText), cancellationToken); break; case "details": var detailOrders = await GetOrders(); Attachment detailOrdersAttachment = GetOrdersAttachment(detailOrders); await turnContext.SendActivityAsync(MessageFactory.Attachment(detailOrdersAttachment), cancellationToken); await _dialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);; break; default: await turnContext.SendActivityAsync(MessageFactory.Text( "Hmmm... I don't know how to help you with that.\n\n" + "Try saying `help` to see what I can do." ), cancellationToken); break; } } public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { await base.OnTurnAsync(turnContext, cancellationToken); // Save any state changes that might have occurred during the turn. await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); await _userState.SaveChangesAsync(turnContext, false, cancellationToken); } ... } OrderDetailDialog.cs
public class OrderDetailDialog : ComponentDialog { private readonly IOrderRepo _orderRepo; public OrderDetailDialog(UserState userState, IOrderRepo orderRepo) : base(nameof(OrderDetailDialog)) { _orderRepo = orderRepo; var waterfallSteps = new WaterfallStep[] { OrdersStep, ViewOrderDetailsStep }; AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps)); AddDialog(new NumberPrompt<int>(nameof(NumberPrompt<int>), OrderNumberPromptValidatorAsync)); // The initial child Dialog to run. InitialDialogId = nameof(WaterfallDialog); } private static async Task<DialogTurnResult> OrdersStep(WaterfallStepContext stepContext, CancellationToken cancellationToken) { return await stepContext.PromptAsync(nameof(NumberPrompt<int>), new PromptOptions { Prompt = MessageFactory.Text("Please enter the order number."), RetryPrompt = MessageFactory.Text("The number must be from the list.") }, cancellationToken); } private static async Task<DialogTurnResult> ViewOrderDetailsStep(WaterfallStepContext stepContext, CancellationToken cancellationToken) { stepContext.Values["orderNumber"] = ((FoundChoice)stepContext.Result).Value; return await stepContext.EndDialogAsync(GetOrderDetailsCard(), cancellationToken); } private async Task<bool> OrderNumberPromptValidatorAsync(PromptValidatorContext<int> promptContext, CancellationToken cancellationToken) { var orders = await _orderRepo.GetOrders(); return promptContext.Recognized.Succeeded && orders.Select(x => x.Id).Contains(promptContext.Recognized.Value); } private static Attachment GetOrderDetailsCard() { var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0)); card.Body.Add(new AdaptiveTextBlock("order details card attachment")); Attachment attachment = new Attachment() { ContentType = "application/vnd.microsoft.card.adaptive", Content = card }; return attachment; } }