Prompt And Waterfall Dialog In Bot V4 Framework Bot Builder 😍 .NET Core

A Dialog is like a function in the bot framework. The main aim of Dialog is to make a proper structure for the Bot implementation and to avoid repetition. Dialogs take care of the sequential way of calling the functions and the initial level of the validations. First, we will learn how the dialogs are maintained in the Bot Framework.

The following keywords and functions are used to construct the dialogs in the bot framework: DialogSet, DialogContext, CreateContext, BeginDialogAsync, ActiveDialog, ContinueDialogAsync, and EndDialogAsync.

Overview of APIs

DialogSet contains all the dialogs (DialogSet is a parent and Dialogs are children). If any dialog is created in the Bot, it should be added into the DialogSet.

Note

We can access all the dialogs only via DialogSet.

  • DialogContext: DialogContext is used for maintaining the “DialogState” information (where the dialog is currently present in the DialogSet).
  • CreateContext: Request to the DialogSet to get the DialogContext. Initially, DialogSet returns null. Otherwise, it returns the current ActiveDialog. If DialogSet is null, it calls the BeginDialogAsync set of the ongoing dialog.
  • ContinueDialogAsync: If the active dialog is not null, then we call the ContiueDialogAsync to move the DialogContext into the next dialog (DialogSet collection processes the dialog one by one via the ContinueDialogAsync function.)
  • EndDialogAsync: Once all the DialogSet collection has been processed, DialogSet internally calls the “EndDialogAsync” function.

Note. All Dialogs must have the DialogId (string key) to add to the DialogSet.

Dialog Types

Dialogs are classified into three different categories.

  1. Prompt Dialog
  2. Waterfall Dialog
  3. Component Dialog

Validation is handled by the Prompt dialog.

The sequential way of calling a function is taken care of by the Waterfall dialog

The component dialog is used for reusable dialog to create the specific need of the dialog. For example - user information form.

Prompt Dialog

The primary goal of PromptDialog is an easy way to get input from the user and validate the data. PromptDialog handles two types of validations.

  1. InBuilt in Validation
  2. Custom validation

Below are different types of PromptDialogs.

InBuilt prompt dialog: InBuilt prompt dialog is handled in a two-step process - 1. Get the user input 2. Validate the data. If it's a success, it returns the value, otherwise, it re-prompts the user to enter the valid user input.

Prompt Dialog Type Use of the Dialog Return value
Text Get Text input from the user String
Number Get number input from the user Number
Date-time Get the DateTime value from the user Datetime
Choice Prompt Show the collection of the input to the user, In The user has to select the option Choice object
Confirm Confirmation from the user Bool
Attachment Option to the user-attached document or image Collection of connected objects


Waterfall Dialog

Waterfall dialog is used to collect the series of input from the users, i.e., it means, you can define in your bot, which one executed first and second (ex: User Name, Age, DOB,..).

Once the waterfall method has been initiated, each step (function) takes care of two functionalities: 1. Process the result of the previous step 2. Next questions to the user.

Note

Waterfall dialog & Prompt dialog are inter-related. The Prompt dialog controls waterfall steps. (Below is a diagram example of Waterfall and Prompt dialog workflow)

Once the Begin process has started, we receive the user data in step 2. Based on the prompt, data gets validated by the prompt dialog. If the prompt is successful, control passes to level 3, otherwise, control is passed to step 1.

How to implement Prompt & Waterfall dialog?

In this section, we are going to learn how to implement the Prompt & Waterfall dialog. There are three steps to achieve this concept in the Bot framework.

  1. Add the Prompt dialog types in DialogSet with DialogId as a Key
  2. Add the dialog function name in the Waterfall dialog, and this should be a delegate function and return the DialogTurnResult object.
  3. Add one more function delegate function to handle the result value from the user.
  4. Create a delegate function with the same function name that we have defined in the waterfall step with the DialogId key defined in the DialogSet

Implement the delegate function

Once the structure of the role has been defined, we should use the PromptOptions & MessageFactory class to create a message and use the PromptAsync function to inform the user with the help of WaterfallStepContext.

How to get the value from the user? step 3 above.

If a Bot framework handles one query, two functions get involved, one function handles the question, another one handles the answers. Waterfall works the same way.

Create a relation function with the same arguments, and use the WaterfallStepContext to get the result.

Let's see one example of how to implement this feature. In this example, we are going to read the username, mobile number, language list, and datetime.

// Copyright (c) Microsoft Corporation. All rights reserved.  
// Licensed under the MIT License.  
using System;  
using System.Collections.Generic;  
using System.Threading;  
using System.Threading.Tasks;  
using Microsoft.Bot.Builder;  
using Microsoft.Bot.Builder.Dialogs;  
using Microsoft.Bot.Builder.Dialogs.Choices;  
using Microsoft.Bot.Schema;  
namespace BotPrompt  
{  
    /// <summary>  
    /// Represents a bot that processes incoming activities.  
    /// For each user interaction, an instance of this class is created and the OnTurnAsync method is called.  
    /// This is a Transient lifetime service. Transient lifetime services are created  
    /// each time they're requested. Objects that are expensive to construct, or have a lifetime  
    /// beyond a single turn, should be carefully managed.  
    /// For example, the <see cref="MemoryStorage"/> object and associated  
    /// <see cref="IStatePropertyAccessor{T}"/> object are created with a singleton lifetime.  
    /// </summary>  
    /// <seealso cref="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1"/>  
    public class BotPromptBot : IBot  
    {  
        /// <summary>  
        /// Initializes a new instance of the class.  
        /// </summary>  
        private readonly DialogSet _dialogSet;  
        private readonly StateAccessor _stateAccessor;  
  
        private readonly string DlgMainId = "MainDialog";  
        private readonly string DlgNameId = "NameDlg";  
        private readonly string DlgMobileId = "MobileDlg";  
        private readonly string DlgLanguageId = "LanguageListDlg";  
        private readonly string DlgDateTimeId = "DateTimeDlg";  
        public BotPromptBot(StateAccessor stateAccessor)  
        {  
            _stateAccessor = stateAccessor;  
            _dialogSet = new DialogSet(stateAccessor.DlgState);  
            _dialogSet.Add(new TextPrompt(DlgNameId, UserNameValidation));  
            _dialogSet.Add(new NumberPrompt<int>(DlgMobileId, MobileNumberValidation));  
            _dialogSet.Add(new ChoicePrompt(DlgLanguageId, ChoiceValidataion));  
            _dialogSet.Add(new DateTimePrompt(DlgDateTimeId));  
  
            var waterfallSteps = new WaterfallStep[]  
            {  
                UserNameAsync,  
                GetUserNameAsync,  
                MobileNumberAsync,  
                SelectLanguageList,  
                DateTimeAsync,  
            };  
  
            _dialogSet.Add(new WaterfallDialog(DlgMainId, waterfallSteps));  
        }  
        private Task<bool> ChoiceValidataion(PromptValidatorContext<FoundChoice> promptContext, CancellationToken cancellationToken)  
        {  
            return Task.FromResult(true);  
        }  
        private Task<bool> UserNameValidation(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)  
        {  
            return Task.FromResult(true);  
        }  
        private async Task<bool> MobileNumberValidation(PromptValidatorContext<int> promptContext, CancellationToken cancellationToken)  
        {  
            if (!promptContext.Recognized.Succeeded)  
            {  
                await promptContext.Context.SendActivityAsync("Hello, Please enter the valid mobile no",  
                    cancellationToken: cancellationToken);  
  
                return false;  
            }  
            int count = Convert.ToString(promptContext.Recognized.Value).Length;  
            if (count != 10)  
            {  
                await promptContext.Context.SendActivityAsync("Hello, you are missing some number!!!",  
                    cancellationToken: cancellationToken);  
                return false;  
            }  
            return true;  
        }  
        private async Task<DialogTurnResult> GetUserNameAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)  
        {  
            var name = (string)stepContext.Result;  
  
            return await stepContext.PromptAsync(DlgMobileId, new PromptOptions  
            {  
                Prompt = MessageFactory.Text("Please enter the mobile No"),  
                RetryPrompt = MessageFactory.Text("Enter Valid mobile No")  
            }, cancellationToken);  
        }  
        private async Task<DialogTurnResult> UserNameAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)  
        {  
            return await stepContext.PromptAsync(DlgNameId, new PromptOptions  
            {  
                Prompt = MessageFactory.Text("Hello!!!, Please enter the Name")  
            }, cancellationToken);  
        }  
        private async Task<DialogTurnResult> MobileNumberAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)  
        {  
            var mobileNo = stepContext.Result;  
  
            var newMovieList = new List<string> { "Tamil", "English", "kaanda" };  
  
            return await stepContext.PromptAsync(DlgLanguageId, new PromptOptions  
            {  
                Prompt = MessageFactory.Text("Please select the Language"),  
                Choices = ChoiceFactory.ToChoices(newMovieList),  
                RetryPrompt = MessageFactory.Text("Select from the List")  
            }, cancellationToken);  
        }  
        private async Task<DialogTurnResult> SelectLanguageList(WaterfallStepContext stepContext, CancellationToken cancellationToken)  
        {  
            var choice = (FoundChoice)stepContext.Result;  
  
            return await stepContext.PromptAsync(DlgDateTimeId, new PromptOptions  
            {  
                Prompt = MessageFactory.Text("Please select the Date")  
            }, cancellationToken);  
        }  
        private async Task<DialogTurnResult> DateTimeAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)  
        {  
            var datetime = stepContext.Result;  
  
            return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);  
        }  
  
        /// <summary>  
        /// Every conversation turn calls this method.  
        /// </summary>  
        /// <param name="turnContext">A <see cref="ITurnContext"/> containing all the data needed  
        /// for processing this conversation turn. </param>  
        /// <param name="cancellationToken">(Optional) A <see cref="CancellationToken"/> that can be used by other objects  
        /// or threads to receive notice of cancellation.</param>  
        /// <returns>A <see cref="Task"/> that represents the work queued to execute.</returns>  
        /// <seealso cref="BotStateSet"/>  
        /// <seealso cref="ConversationState"/>  
        public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))  
        {  
            // Handle Message activity type, which is the main activity type for shown within a conversational interface  
            // Message activities may contain text, speech, interactive cards, and binary or unknown attachments.  
            // see https://aka.ms/about-bot-activity-message to learn more about the message and other activity types  
            if (turnContext.Activity.Type == ActivityTypes.Message)  
            {  
                DialogContext dlgContext = await _dialogSet.CreateContextAsync(turnContext, cancellationToken: cancellationToken);  
  
                if (dlgContext != null && dlgContext.ActiveDialog is null)  
                {  
                    await dlgContext.BeginDialogAsync(DlgMainId, null, cancellationToken);  
                }  
                else if (dlgContext != null && dlgContext.ActiveDialog != null)  
                {  
                    await dlgContext.ContinueDialogAsync(cancellationToken);  
                }  
                await _stateAccessor.Conversation.SaveChangesAsync(turnContext, false, cancellationToken);  
            }  
        }  
    }  
}  

Once all the queries have been completed, finally call the EndDialogAsync to complete dialog functionality.

You can find the complete sample here.

Conclusion

I hope you understand how to implement the Prompt waterfall dialogs. In the next article, we will see how to implement the custom validation.

Happy Reading. Happy coding.


Similar Articles