Bot Framework v4 Dialog Prompt and Waterfall


In the post, Overview of Dialogs for Bot Framework v4, it covered what a dialog is and how it works. In addition, it covered the different types of prompt and what they are used for. For this post, I am going to be going over an example of using a waterfall dialog with each step being a unique prompt type.

 

Before going forward, I recommend going over the overview of dialogs post if you haven’t already.

 

Note: The code for this example is added on top of the generated code when you create a bot web app on Azure. Refer to my post on getting started with bot framework development if you are not sure how to obtain the generated code.


 

Example

 

For the example, there will be one waterfall dialog with a total of 6 steps. In each step, a different prompt type will be used. The end result is a waterfall dialog that asks for different types of information with the different prompt types.

 

ExamplePromptsDialog

 

For this example, I will be creating a child waterfall dialog class (ExamplePromptsDialog) that inherits from the WaterfallDialog class. Within this class is where each step of the waterfall dialog is defined. When the dialog starts from the BeginDialogAsync method call, after initialization of the waterfall dialog object the first step runs.

 

Note: In the constructor, you will need to add the first step. In addition, the dialog needs to have an id that is unique to it so it can be referred to in the DialogSet.

 

public class ExamplePromptsDialog : WaterfallDialog
{
    public ExamplePromptsDialog(
        string dialogId,
        IEnumerable<WaterfallStep> steps = null
    )
    : base(dialogId, steps)
    {
        AddStep(AskForATextResponseAsync);
    }

    private async Task<DialogTurnResult> AskForATextResponseAsync(
        WaterfallStepContext stepContext,
        CancellationToken cancellationToken)
    {
        // ...
    }

    public static string Id = "examplePromptsDialogId";

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

 

Text Prompt

 

The text prompt is used to ask the user for a text reply. Behind the scene, it is a two-part dialog, with the first asking a question. The second is validating the response and returning the response as a string.

 

For the example, I’ll create a text prompt to ask the user for their name.

 

private async Task<DialogTurnResult> AskForATextResponseAsync(
    WaterfallStepContext stepContext,
    CancellationToken cancellationToken)
{
    Activity textPrompt = stepContext.Context.Activity.CreateReply("What is your name?");

    PromptOptions promptOptions = new PromptOptions
    {
        Prompt = textPrompt
    };

    return await stepContext.PromptAsync(TextPromptId, promptOptions, cancellationToken);
}

 

 

Confirmation Prompt

 

The confirmation prompt is used to ask the user to confirm if something is correct. Behind the scene, it is a two-part dialog, with the first asking a question and showing two buttons (yes and no). The second is validating the response and returning the response as a FoundChoice.

 

For the example, I’ll create a confirmation prompt that uses the name provided by the user via Text Prompt and ask if the name is correct.

 

private async Task<DialogTurnResult> AskForAConfirmationAsync(
    WaterfallStepContext stepContext,
    CancellationToken cancellationToken)
{
    var name = stepContext.Context.Activity?.Text;

    Activity textPrompt = stepContext.Context.Activity.CreateReply($"Your name is {name}. Is that correct?");
    PromptOptions promptOptions = new PromptOptions
    {
        Prompt = textPrompt
    };

    return await stepContext.PromptAsync(ConfirmPromptId, promptOptions, cancellationToken);
}

 

 

Number Prompt

 

The number prompt is used to ask the user to provide a numeric value. Behind the scene, it is a two-part dialog, with the first showing a prompt. The second is validating if the user reply is a number and returns the reply as a numeric value (you can specify if it is an int, float, double, etc when you first create the number prompt). If the reply is not a numeric value the number prompt will ask the user for a number again.

 

For this example, I’ll create a number prompt that asks the user to input their favorite number.

 

private async Task<DialogTurnResult> AskForANumberAsync(
    WaterfallStepContext stepContext,
    CancellationToken cancellationToken)
{
    Activity activity = stepContext.Context.Activity.CreateReply("What is your favorite number?");
    PromptOptions promptOptions = new PromptOptions
    {
        Prompt = activity
    };

    return await stepContext.PromptAsync(NumberPromptId, promptOptions, cancellationToken);
}

 

 

Date-Time Prompt

 

The date-time prompt is used to ask the user to provide a date and or a time. Behind the scene, it is a two-part dialog, with the first showing a prompt. The second is validating if the user reply is a mixture of date and time and return the reply broken down into date and time components. If the reply is not a date and or time then the date-time prompt will re-prompt the user to provide a date and or time.

 

For this example, I’ll create a date-time prompt that asks the user to provide their birthday.

 

private async Task<DialogTurnResult> AskForADateAndTimeAsync(
    WaterfallStepContext stepContext,
    CancellationToken cancellationToken)
{
    Activity activity = stepContext.Context.Activity.CreateReply("When is your birthday?");
    Activity retryActivity = stepContext.Context.Activity.CreateReply("I am not really sure what you said...Can you say tell me the month and day of your birthday ?");

    PromptOptions promptOptions = new PromptOptions
    {
        Prompt = activity,
        RetryPrompt = retryActivity
    };

    return await stepContext.PromptAsync(DateTimePromptId, promptOptions, cancellationToken);
}

Choice Prompt

 

The choice prompt is used to provide the user with a list of options to choose from. Behind the scene, it is a two-part dialog, with the first showing a prompt and a list of buttons for each choice. The second is validating if the user reply is one of the choices and then return the chosen option. If the user’s reply is not in the list of options (they manually typed a response instead of selecting a button) then the choice prompt will re-prompt the user with the available options again.

 

For this example, I’ll create a choice prompt that asks what the user like to eat and provide three options (meat, vegetables, and both).

 

private async Task<DialogTurnResult> AskForAChoiceAsync(
    WaterfallStepContext stepContext,
    CancellationToken cancellationToken)
{
    Activity textPrompt = stepContext.Context.Activity.CreateReply("What do you like to eat?");
    PromptOptions promptOptions = new PromptOptions
    {
        Prompt = textPrompt,
        Choices = new List<Choice> { new Choice("meat"), new Choice("vegetables"), new Choice("meat and vegatables") }
    };

    return await stepContext.PromptAsync(ChoicePromptId, promptOptions, cancellationToken);
}

 

 

Attachment Prompt

 

The attachment prompt is used to ask the user to provide some form of data (e.g. photo or document). Behind the scene, it is a two-part dialog, with the first showing a prompt. The second validating if the user uploaded some type of document(s) and return the uploaded documents as attachments.

 

For this example, I’ll create an attachment prompt that asks the user to provide their favorite photo.

 

private async Task<DialogTurnResult> AskForAnAttachmentAsync(
    WaterfallStepContext stepContext,
    CancellationToken cancellationToken)
{
    Activity favoritePictureActivity = stepContext.Context.Activity.CreateReply("Can you upload your favorite picture or document?");
    Activity retryFavoritePictureActivity = stepContext.Context.Activity.CreateReply("I can only understand documents or pictures. Can you upload your favorite picture or document?");
    PromptOptions promptOptions = new PromptOptions
    {
        Prompt = favoritePictureActivity,
        RetryPrompt = retryFavoritePictureActivity
    };

    await stepContext.PromptAsync(AttachmentPromptId, promptOptions, cancellationToken);

    return await stepContext.EndDialogAsync();
}

 

 

Tying It All Together

 

For the prompts and dialog to be usable, they all need to be added into the DialogSet of the bot. To add to the DialogSet, you’ll need to add a new instance of the waterfall dialog and the prompts. For the generated project, this is in the constructor of the BasicBot.cs file.

 

public BasicBot(BotServices services, UserState userState, ConversationState conversationState, ILoggerFactory loggerFactory)
{
    // ...
    Dialogs = new DialogSet(_dialogStateAccessor);
    Dialogs.Add(new GreetingDialog(_greetingStateAccessor, loggerFactory));
    Dialogs.Add(ExamplePromptsDialog.Instance);
    Dialogs.Add(new TextPrompt(ExamplePromptsDialog.TextPromptId));
    Dialogs.Add(new ChoicePrompt(ExamplePromptsDialog.ChoicePromptId));
    Dialogs.Add(new AttachmentPrompt(ExamplePromptsDialog.AttachmentPromptId));
    Dialogs.Add(new ConfirmPrompt(ExamplePromptsDialog.ConfirmPromptId));
    Dialogs.Add(new DateTimePrompt(ExamplePromptsDialog.DateTimePromptId));
    Dialogs.Add(new NumberPrompt<int>(ExamplePromptsDialog.NumberPromptId));
    // ...
}

 

Once the dialogs and prompts are in the DialogeSet, you can start the dialog with the BeginDialogAsync method in the BasicBot

 

await dc.BeginDialogAsync(ExamplePromptsDialog.Id);

 

Lastly, here is the full Waterfall Dialog with the prompts.

 

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Choices;
using Microsoft.Bot.Schema;

namespace BasicBot.Dialogs.ExamplePrompts
{
    public class ExamplePromptsDialog : WaterfallDialog
    {
        public static string TextPromptId = "textPromptId";
        public static string AttachmentPromptId = "attachmentPromptId";
        public static string ChoicePromptId = "choicePromptId";
        public static string ConfirmPromptId = "confirmPromptId";
        public static string NumberPromptId = "numberPromptId";
        public static string DateTimePromptId = "dateTimePromptId";

        public ExamplePromptsDialog(
            string dialogId,
            IEnumerable<WaterfallStep> steps = null
        )
            : base(dialogId, steps)
        {
            AddStep(AskForATextResponseAsync);
            AddStep(AskForAConfirmationAsync);
            AddStep(AskForANumberAsync);
            AddStep(AskForADateAndTimeAsync);
            AddStep(AskForAChoiceAsync);
            AddStep(AskForAnAttachmentAsync);
        }

        private async Task<DialogTurnResult> AskForAnAttachmentAsync(
            WaterfallStepContext stepContext,
            CancellationToken cancellationToken)
        {
            Activity favoritePictureActivity = stepContext.Context.Activity.CreateReply("Can you upload your favorite picture or document?");
            Activity retryFavoritePictureActivity = stepContext.Context.Activity.CreateReply("I can only understand documents or pictures. Can you upload your favorite picture or document?");
            PromptOptions promptOptions = new PromptOptions
            {
                Prompt = favoritePictureActivity,
                RetryPrompt = retryFavoritePictureActivity
            };

            await stepContext.PromptAsync(AttachmentPromptId, promptOptions, cancellationToken);

            return await stepContext.EndDialogAsync();
        }
        
        private async Task<DialogTurnResult> AskForAChoiceAsync(
            WaterfallStepContext stepContext,
            CancellationToken cancellationToken)
        {
            Activity textPrompt = stepContext.Context.Activity.CreateReply("What do you like to eat?");
            PromptOptions promptOptions = new PromptOptions
            {
                Prompt = textPrompt,
                Choices = new List<Choice> { new Choice("meat"), new Choice("vegetables"), new Choice("meat and vegatables") }
            };

            return await stepContext.PromptAsync(ChoicePromptId, promptOptions, cancellationToken);
        }

        private async Task<DialogTurnResult> AskForAConfirmationAsync(
            WaterfallStepContext stepContext,
            CancellationToken cancellationToken)
        {
            var name = stepContext.Context.Activity?.Text;

            Activity textPrompt = stepContext.Context.Activity.CreateReply($"Your name is {name}. Is that correct?");
            PromptOptions promptOptions = new PromptOptions
            {
                Prompt = textPrompt
            };

            return await stepContext.PromptAsync(ConfirmPromptId, promptOptions, cancellationToken);
        }

        private async Task<DialogTurnResult> AskForADateAndTimeAsync(
            WaterfallStepContext stepContext,
            CancellationToken cancellationToken)
        {
            Activity activity = stepContext.Context.Activity.CreateReply("When is your birthday?");
            Activity retryActivity = stepContext.Context.Activity.CreateReply("I am not really sure what you said...Can you say tell me the month and day of your birthday ?");

            PromptOptions promptOptions = new PromptOptions
            {
                Prompt = activity,
                RetryPrompt = retryActivity
            };

            return await stepContext.PromptAsync(DateTimePromptId, promptOptions, cancellationToken);
        }

        private async Task<DialogTurnResult> AskForANumberAsync(
            WaterfallStepContext stepContext,
            CancellationToken cancellationToken)
        {
            Activity activity = stepContext.Context.Activity.CreateReply("What is your favorite number?");
            PromptOptions promptOptions = new PromptOptions
            {
                Prompt = activity
            };

            return await stepContext.PromptAsync(NumberPromptId, promptOptions, cancellationToken);
        }

        private async Task<DialogTurnResult> AskForATextResponseAsync(
            WaterfallStepContext stepContext,
            CancellationToken cancellationToken)
        {
            Activity textPrompt = stepContext.Context.Activity.CreateReply("What is your name?");

            PromptOptions promptOptions = new PromptOptions
            {
                Prompt = textPrompt
            };

            return await stepContext.PromptAsync(TextPromptId, promptOptions, cancellationToken);
        }

        public static string Id = "examplePromptsDialogId";

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

 

I hope this post was helpful to you. If you found this post helpful, share it with others so they can benefit too.

 

What are your experiences working with dialog using the bot framework?

 

To get in touch, you can follow me on Twitter, leave a comment, or send me an email at steven@brightdevelopers.com.


About Steven To

Steven To is a software developer that specializes in mobile development with a background in computer engineering. Beyond his passion for software development, he also has an interest in Virtual Reality, Augmented Reality, Artificial Intelligence, Personal Development, and Personal Finance. If he is not writing software, then he is out learning something new.