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

This article explains the concept of Prompt & Waterfall Dialogs in Bot Framework SDK 4.

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.

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 ContinueDialogAsync function.)

  • EndDialogAsync
    Once all the DialogSet collection has been processed, DialogSet internally calls the “EndDialogAsync” function.
All Dialogs must have the DialogId (string key) to add into the DialogSet.
Prompt And Waterfall Dialog In Bot V4 Framework Bot Builder.NET Core

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 DateTime value from the user Datetime
Choice Prompt Show the collection of the input to the user, In list user has 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 initiated, each step (function) takes care of two functionalities: 1. Process the result of previous step 2. Next questions to the user.
Waterfall dialog & Prompt dialog are inter-related. The Prompt dialog controls waterfall steps. (Below is diagram example of Waterfall and Prompt dialog work flow)

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.
 Prompt And Waterfall Dialog In Bot V4 Framework Bot Builder.NET Core

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 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 in 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.
  1. // Copyright (c) Microsoft Corporation. All rights reserved.  
  2. // Licensed under the MIT License.  
  4. using System;  
  5. using System.Collections.Generic;  
  6. using System.Threading;  
  7. using System.Threading.Tasks;  
  8. using Microsoft.Bot.Builder;  
  9. using Microsoft.Bot.Builder.Dialogs;  
  10. using Microsoft.Bot.Builder.Dialogs.Choices;  
  11. using Microsoft.Bot.Schema;  
  13. namespace BotPrompt  
  14. {  
  15.     /// <summary>  
  16.     /// Represents a bot that processes incoming activities.  
  17.     /// For each user interaction, an instance of this class is created and the OnTurnAsync method is called.  
  18.     /// This is a Transient lifetime service. Transient lifetime services are created  
  19.     /// each time they're requested. Objects that are expensive to construct, or have a lifetime  
  20.     /// beyond a single turn, should be carefully managed.  
  21.     /// For example, the <see cref="MemoryStorage"/> object and associated  
  22.     /// <see cref="IStatePropertyAccessor{T}"/> object are created with a singleton lifetime.  
  23.     /// </summary>  
  24.     /// <seealso cref=""/>  
  25.     public class BotPromptBot : IBot  
  26.     {  
  27.         /// <summary>  
  28.         /// Initializes a new instance of the class.  
  29.         /// </summary>  
  30.         ///  
  31.         private readonly DialogSet _dialogSet;  
  32.         private readonly StateAccessor _stateAccessor;  
  34.         private readonly string DlgMainId = "MainDialog";  
  35.         private readonly string DlgNameId = "NameDlg";  
  36.         private readonly string DlgMobileId = "MobileDlg";  
  37.         private readonly string DlgLanguageId = "LanguageListDlg";  
  38.         private readonly string DlgDateTimeId = "DateTimeDlg";  
  40.         public BotPromptBot(StateAccessor stateAccessor)  
  41.         {  
  42.             _stateAccessor = stateAccessor;  
  43.             _dialogSet = new DialogSet(stateAccessor.DlgState);  
  44.             _dialogSet.Add(new TextPrompt(DlgNameId,UserNameValidation));  
  45.             _dialogSet.Add(new NumberPrompt<int>(DlgMobileId,MobileNumberValidation));  
  46.             _dialogSet.Add(new ChoicePrompt(DlgLanguageId,ChoiceValidataion));  
  47.             _dialogSet.Add(new DateTimePrompt(DlgDateTimeId));  
  49.             var waterfallSteps = new WaterfallStep[]  
  50.             {  
  51.                 UserNameAsync,  
  52.                 GetUserNameAsync,  
  53.                 MobileNumberAsync,  
  54.                 SelectLanguageList,  
  55.                 DateTimeAsync,  
  56.             };  
  58.             _dialogSet.Add(new WaterfallDialog(DlgMainId, waterfallSteps));  
  62.         }  
  64.         private Task<bool> ChoiceValidataion(PromptValidatorContext<FoundChoice> promptContext, CancellationToken cancellationToken)  
  65.         {  
  66.             return Task.FromResult(true);  
  67.         }  
  69.         private Task<bool> UserNameValidation(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)  
  70.         {  
  71.             return Task.FromResult(true);  
  72.         }  
  74.         private async Task<bool> MobileNumberValidation(PromptValidatorContext<int> promptcontext, CancellationToken cancellationtoken)  
  75.         {  
  76.             if (!promptcontext.Recognized.Succeeded)  
  77.             {  
  78.                 await promptcontext.Context.SendActivityAsync("Hello, Please enter the valid mobile no",  
  79.                     cancellationToken: cancellationtoken);  
  81.                 return false;  
  82.             }  
  84.             int count = Convert.ToString(promptcontext.Recognized.Value).Length;  
  85.             if (count != 10)  
  86.             {  
  87.                 await promptcontext.Context.SendActivityAsync("Hello , you are missing some number !!!",  
  88.                     cancellationToken: cancellationtoken);  
  89.                 return false;  
  90.             }  
  92.             return true;  
  93.         }  
  95.         private async Task<DialogTurnResult> GetUserNameAsync(WaterfallStepContext stepcontext, CancellationToken cancellationtoken)  
  96.         {  
  97.             var name = (string)stepcontext.Result;  
  99.             return await stepcontext.PromptAsync(DlgMobileId, new PromptOptions()  
  100.             {  
  101.                 Prompt = MessageFactory.Text("Please enter the mobile No"),  
  102.                 RetryPrompt = MessageFactory.Text("Enter Valid mobile No")  
  103.             }, cancellationtoken);  
  104.         }  
  106.         private async Task<DialogTurnResult> UserNameAsync(WaterfallStepContext stepcontext, CancellationToken cancellationtoken)  
  107.         {  
  108.             return await stepcontext.PromptAsync(DlgNameId, new PromptOptions  
  109.             {  
  110.                 Prompt = MessageFactory.Text("Hello !!!, Please enter the Name")  
  112.             }, cancellationtoken);  
  113.         }  
  115.         private async Task<DialogTurnResult> MobileNumberAsync(WaterfallStepContext stepContext,  
  116.             CancellationToken cancellationtoken)  
  117.         {  
  118.             var mobileNo = stepContext.Result;  
  120.             var newMovieList = new List<string> {" Tamil "" English "" kaanda "};  
  122.             return await stepContext.PromptAsync(DlgLanguageId, new PromptOptions()  
  123.             {  
  124.                 Prompt = MessageFactory.Text("Please select the Language"),  
  125.                 Choices = ChoiceFactory.ToChoices(newMovieList),  
  126.                 RetryPrompt = MessageFactory.Text("Select from the List")  
  127.             },cancellationtoken);  
  128.         }  
  130.         private async Task<DialogTurnResult> SelectLanguageList(WaterfallStepContext stepContext,  
  131.             CancellationToken cancellationToken)  
  132.         {  
  133.             var choice = (FoundChoice) stepContext.Result;  
  135.             return await stepContext.PromptAsync(DlgDateTimeId, new PromptOptions()  
  136.             {  
  137.                 Prompt = MessageFactory.Text("Please select the Date")  
  138.             },cancellationToken);  
  139.         }  
  141.         private async Task<DialogTurnResult> DateTimeAsync(WaterfallStepContext stepcontext, CancellationToken cancellationtoken)  
  142.         {  
  143.             var datetime = stepcontext.Result;  
  145.             return await stepcontext.EndDialogAsync(cancellationToken: cancellationtoken);  
  146.         }  
  149.         /// <summary>  
  150.         /// Every conversation turn calls this method.  
  151.         /// </summary>  
  152.         /// <param name="turnContext">A <see cref="ITurnContext"/> containing all the data needed  
  153.         /// for processing this conversation turn. </param>  
  154.         /// <param name="cancellationToken">(Optional) A <see cref="CancellationToken"/> that can be used by other objects  
  155.         /// or threads to receive notice of cancellation.</param>  
  156.         /// <returns>A <see cref="Task"/> that represents the work queued to execute.</returns>  
  157.         /// <seealso cref="BotStateSet"/>  
  158.         /// <seealso cref="ConversationState"/>  
  159.         public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))  
  160.         {  
  161.             // Handle Message activity type, which is the main activity type for shown within a conversational interface  
  162.             // Message activities may contain text, speech, interactive cards, and binary or unknown attachments.  
  163.             // see to learn more about the message and other activity types  
  164.             if (turnContext.Activity.Type == ActivityTypes.Message)  
  165.             {  
  167.                 DialogContext dlgContext =  
  168.                     await _dialogSet.CreateContextAsync(turnContext, cancellationToken: cancellationToken);  
  170.                 if (dlgContext != null && dlgContext.ActiveDialog is null)  
  171.                 {  
  172.                     await dlgContext.BeginDialogAsync(DlgMainId, null, cancellationToken);  
  173.                 }  
  174.                 else if (dlgContext != null && dlgContext.ActiveDialog != null)  
  175.                 {  
  176.                     await dlgContext.ContinueDialogAsync(cancellationToken);  
  177.                 }  
  179.                 await _stateAccessor.Conversation.SaveChangesAsync(turnContext, false, cancellationToken);  
  181.                 // Echo back to the user whatever they typed.               
  182.                // await turnContext.SendActivityAsync("Hello World", cancellationToken: cancellationToken);  
  183.             }  
  184.         }  
  185.     }  
  186. }  
Once all the queries have completed, finally call the EndDialogAsync to complete dialog functionality.
You can find the complete sample here.


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.