Create User Defined Dialogs in Bot V4 Framework Bot Builder 😍 .NET Core

In this article, we are going to learn how to write a user-defined Dialog class. We will also see its best practices.

Before reading this article, I highly recommend you read the Dialogs in Bot and Custom validation in Bot. First, why do we want to overwrite the Dialog class? See the below code. To read the username for this property, we have to write two functions - one function asks the question and the other one reads the value.
  1. private async Task<DialogTurnResult> UserNameAsync(WaterfallStepContext stepcontext, CancellationToken cancellationtoken)        
  2. {        
  3.     return await stepcontext.PromptAsync(DlgNameId, new PromptOptions        
  4.     {        
  5.         Prompt = MessageFactory.Text("Hello !!!, Please enter the Name")        
  6.                        
  7.     }, cancellationtoken);        
  8. }      
  9.      
  10. private async Task<DialogTurnResult> GetUserNameAsync(WaterfallStepContext stepcontext, CancellationToken cancellationtoken)        
  11. {        
  12.     var name = (string)stepcontext.Result;   
  13. }  
Let’s imagine we need 20 different properties from the user. Then, how many functions do we have to write? This is not good, right? The code maintainability is out of control.

The answer to this problem is - Overwrite the dialog class.
 
Dialog class has BeginDialogAsync, ResumeDialogAsync, & EndDialogAsync functions. We are going to use those functions to implement Reusable dialog.

Let's take one example and see how to solve this problem.

Example: Our requirement is to get the username, mobile number, city name, and the pizza name. Based on the properties, define the prompt dialog types.
 
User class : Username(TextPrompt), Mobile Number(TextPrompt), City Name(TextPrompt) Pizza class : Pizza Name(choicePrompt)
 

Add the Prompt Dialog in DialogSet with Unique Key

 
Note
All dialogs must be added into the DialogSet.

Add all the prompt dialog types into the DialogSet. That’s also one of the maintainability problems. Here, what we are going to do is to add the one unique PromptDialogtype into the DialogSet and reuse it.
 
In our example - if two prompt texts are required, only one TextPrompt adds into the DialogSet. Use this TextPrompt into two properties with the help of the DialogId.

Add the Prompt type in the DialogSet as in this example.
  1. _dialogSet = new DialogSet(dlgstate);  
  2. _dialogSet.Add(new TextPrompt("text"));  
  3. _dialogSet.Add(new NumberPrompt<int>("number"));  
  4. _dialogSet.Add(new ChoicePrompt("choice"));  
Create a Model

For adding the property into the dialog set, we create a class model and create the property of the DialogSet.
  1. using Microsoft.Bot.Builder.Dialogs;  
  2.   
  3. namespace MeetpupDialog.Model  
  4. {  
  5.     public class Property  
  6.     {  
  7.         public string PromptDlgId;  
  8.   
  9.         public string Name;  
  10.   
  11.         public PromptOptions Pmoptions;  
  12.   
  13.         public Property(string promptDlgId, string name, PromptOptions pmoptions)  
  14.         {  
  15.             this.PromptDlgId = promptDlgId;  
  16.             Name = name;  
  17.             this.Pmoptions = pmoptions;  
  18.         }  
  19.   
  20.         public Property(string promptDlgId,string name,string pmtext=null) :   
  21.             this(promptDlgId,name, new PromptOptions { Prompt = MessageFactory.Text(pmtext) })   
  22.         {  
  23.         }  
  24.     }  

  25.  
  1. Public string PromptDlgId;//the PromptDialogId which has defined in the -_dialogset
  2. public string // Dialog Key name which is used to read the value from the dialogset, later of this article we see more on this in ResumeDialogAsync section.      
  3. Public PromptOptions Pmoptions; // define the prompt options what type of information displaying to the user.  

Create a UserForm & Pizza class


Based on the case, we divided two different classes 1. User form 2. Pizza order
 
Create a UserForm class 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Threading.Tasks;  
  5. using MeetpupDialog.Model;  
  6.   
  7. namespace MeetpupDialog.ChildDialog  
  8. {  
  9.     public static class UserForm  
  10.     {  
  11.         public static List<Property> PromptProperties()  
  12.         {  
  13.             var lstproperty = new List<Property>  
  14.             {  
  15.                 new Property("text""FirstName""Please enter the user Name"),  
  16.                 new Property("number""moblieNo""Please enter the mobile Number")  
  17.             };  
  18.   
  19.             return lstproperty;  
  20.         }  
  21.     }  
  22. }  
Creating a property list, Property “text” is used to ref the DialogSet prompt type.
 
“First Name” is used for Key in the ExecuteChildDialog (later of this dialog, see more on this ) to store the value and final string used for PromptOptions, and what dialog type is shown to the user.
 
Pizza Order class 
  1. using System.Collections.Generic;  
  2. using MeetpupDialog.Model;  
  3. using Microsoft.Bot.Builder;  
  4. using Microsoft.Bot.Builder.Dialogs;  
  5. using Microsoft.Bot.Builder.Dialogs.Choices;  
  6.   
  7. namespace MeetpupDialog.ChildDialog  
  8. {  
  9.     public static class PizzaOrder  
  10.     {  
  11.         public static List<Property> PromptProperties()  
  12.         {  
  13.             var pizzalist = new PromptOptions  
  14.             {  
  15.                 Choices = new List<Choice> {new Choice("Plain Pizza"), new Choice("Pizza with Mushrooms"), new Choice("Pizza3")},  
  16.                 Prompt = MessageFactory.Text("Please select the Pizza Type")  
  17.             };  
  18.   
  19.             var lstpropery = new List<Property>  
  20.             {  
  21.                 new Property("choice","PizzaName",pizzalist)  
  22.             };  
  23.   
  24.             return lstpropery;  
  25.         }  
  26.     }  
  27. }  
The same as UserForm class the difference is Pizza order class using the choice option.
 

Overwrite the Dialog Class


Once our class builds, we have to execute all the properties one by one and get value from the user. This step by step execution is taken care of by CustomDialog.
 
CustomDialog class inherits from the Dialog class & overwrite the BeginDialogAsync, and ResumeDialogAsync function.
 
This function takes care of the execution of the steps, all process is done call the EndDialog function to stop the execution.

Below flow diagram explain about the custom dialog execution  
 
Overview of the above image steps,
  1. BeginDialogAsync trigger the first question
  2. Waiting for user response, once user responded to that
  3. ResumeDialogAsync get invoked and get the value stored into the dictionary
  4. Check and trigger the next property goto step 1, once all the execution did
  5. Call the EndDialog.
Above steps are high-level execution of the dialog process, in code level see more on step 3, step 4, step 5
 

Store data into the Dictionary


As a general, we have to write our collections to store (temporary memory) the data this also a slight problem but not always? How to avoid our own collections? Ans: Internal Dictionary

Dictionary: Dictionary is playing a significant role in a user dialog. Internally Dialog class has the dictionary; we can use this dictionary & store our values, one more advantage of this dictionary is used for maintaining state management also.
 

Use of internal Dictionary


Dictionary object is available in the DialogInstance & this class is used for tracking information for a dialog in the stack. Note: Create our key to store the data into the Dictionary.

First, check if the key is present or not, If not, create a new Dictionary object and store the values in DialogInstance Dictionary.

Sample code for Store the values in DialogInstance Dictionary. 
  1. private static IDictionary<stringobject> GetStoredValue(DialogInstance dialogInstance)  
  2. {  
  3.     if (!dialogInstance.State.TryGetValue(MyDir, out object value))  
  4.     {  
  5.         value = new Dictionary<string,object>();  
  6.         dialogInstance.State.Add(MyDir,value);  
  7.     }  
  8.   
  9.     return (IDictionary<stringobject>) value;  
  10. }  
ResumeDialogAsync

Once the user is responded to Bot query, this function gets invoked. This function is used to store the property value to get the help of GetStoredValue function.
 
Refer
 
In the above model class, the name property is used in this class as Key to store the value in the dictionary. 
  1. public override async Task<DialogTurnResult> ResumeDialogAsync(DialogContext dc, DialogReason reason, object result = null,  
  2.             CancellationToken cancellationToken = new CancellationToken())  
  3. {  
  4.               
  5.     var value = GetStoredValue(dc.ActiveDialog);  
  6.     var exeProp = (string)dc.ActiveDialog.State[Property];  
  7.   
  8.     if (result is FoundChoice resultChoice)  
  9.     {  
  10.         value[exeProp] = resultChoice.Value;  
  11.     }  
  12.     else  
  13.     {  
  14.         value[exeProp] = result;  
  15.     }  
  16.               
  17.     return await ExecuteProperty(dc, cancellation: cancellationToken);  
  18. }  
GetStoredValue return the dictionary object, in this dictionary add the value which is entered by the user.

If condition checks the whether result value is choice prompt or not, If choice prompt read the value from the FoundChoice class else directly read from the result set & store into the dictionary.
 
ExecuteProperty
 
This function is a user-defined function; the Main purpose executes the property one by one. As I said early of this article bot framework is stateless we have to maintain the state management this function handling state management.
 
Ex: If the one question asked to the user the same question should not ask next time(state management); this function takes care of the execution with the help of the dictionary
 
Check the Dictionary if the property key is present or not. If not present, assign the property as a current state and call the BeginDialogAsyn function to get the value from the user. Once the user is responded resume dialog function gets called
  1. private async Task<DialogTurnResult> ExecuteProperty(DialogContext dlgContext, CancellationToken cancellation)  
  2. {  
  3.     var executeprop = GetStoredValue(dlgContext.ActiveDialog);  
  4.     var unexecutedprop = _executeProperty.FirstOrDefault((item) => !executeprop.ContainsKey(item.Name));  
  5.   
  6.     if (unexecutedprop != null)  
  7.     {
  8.         dlgContext.ActiveDialog.State[Property] = unexecutedprop.Name;  
  9.         return await dlgContext.BeginDialogAsync(unexecutedprop.PromptDlgId,unexecutedprop.Pmoptions,cancellationToken:cancellation); 
  10.     }  
  11.     else  
  12.     {
  13.         return await dlgContext.EndDialogAsync(executeprop,cancellation);  
  14.     }  
  15. }  

Add user define properties into the DialogSet


Once the supported class is created, add all the properties into the DialogSet.

Note
All the dialogs must be added into the dialog set otherwise it won't invoke. 
  1. _dialogSet.Add(new ExecuteChildDialog(nameof(UserForm), UserForm.PromptProperties()));  
  2. _dialogSet.Add(new ExecuteChildDialog(nameof(PizzaOrder), PizzaOrder.PromptProperties()));  
Root Dialog
 
This is a user-defined dialog; the purpose of the dialog class is to execute all the child dialogs. In this example, User-defined & PizzaOrder is a child dialog. Why we called as a child class? Let see in details.
 
First should understand how the dialogs are executed? Ans: Dialog execution takes care by the Waterfall Dialog. Waterfall take care execution step by step. In our example, waterfall step dialog first executes the user-defined a dialog and second waterfall step executes the Pizaa order dialog if any dialog is there waterfall execute the next dialog. Look like sound like repetition.
 
Yes, we are doing a repetition, to avoiding this add all the child dialog into the root dialog, lets water fall dialog execute the root dialog and root dialog automatically runs all the child class, so only one-time waterfall start the execution, and final result receives from next waterfall function.
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Threading.Tasks;  
  5. using MeetpupDialog.ChildDialog;  
  6. using MeetpupDialog.Model;  
  7.   
  8.   
  9. namespace MeetpupDialog.MainDialog  
  10. {  
  11.     public static class RootDialog  
  12.     {  
  13.         public static List<Property> PromptProperties()  
  14.         {  
  15.             var lstProperty = new List<Property>()  
  16.             {  
  17.                 new Property(nameof(UserForm),nameof(UserForm)),  
  18.                 new Property(nameof(PizzaOrder),nameof(PizzaOrder))  
  19.             };  
  20.   
  21.             return lstProperty;  
  22.         }  
  23.     }  
  24. }  
Overview of the execution
 
WaterfallDialogStep call the RunMainDialog (user defined) function , this function calls the RootDialog , RootDialog executes the all the child dialogs once all done , call the EndDialog function & pass the Dictionary object . Waterfall step EndMainDlg gets called received the Dictionary object.
  1. private async Task<DialogTurnResult> RunMainDialog(WaterfallStepContext stepcontext, CancellationToken cancellationtoken)  
  2. {  
  3.     return await stepcontext.BeginDialogAsync(nameof(RootDialog), null, cancellationtoken);  
  4. }  
  5.   
  6. private async Task<DialogTurnResult> EndMainDlg(WaterfallStepContext stepcontext, CancellationToken cancellationtoken)  
  7. {  
  8.     if(stepcontext.Result is IDictionary<string,object> result)  
  9.     {  
  10.         var userform = (IDictionary<stringobject>)result[nameof(UserForm)];  
  11.         var display = DisplayResult(userform, stepcontext, cancellationtoken);  
  12.   
  13.         var pizzaform = (IDictionary<stringobject>)result[nameof(PizzaOrder)];  
  14.         display += DisplayResult(pizzaform, stepcontext, cancellationtoken);  
  15.   
  16.         await stepcontext.Context.SendActivityAsync(display, cancellationToken: cancellationtoken);  
  17.     }  
  18.     return await stepcontext.EndDialogAsync(cancellationToken: cancellationtoken);  
  19. }  
  20.   
  21. private static string DisplayResult(IDictionary<stringobject> result, WaterfallStepContext stepcontext, CancellationToken cancellationtoken)  
  22. {  
  23.     string res = string.Empty;  
  24.     foreach (var prop in result)  
  25.     {  
  26.         res += "\n" + prop.Key + " : " + prop.Value;  
  27.     }  
  28.     return res;  
  29. }  
WaterfallDialogStep call the RunMainDialog (user defined) function, this function calls the Root Dialog, Root Dialog executes all the subclass once all done, call the EndDialog function , refer to the ExecuteProperty function if. else condition once all the properties have executed else block we call the EndDialog function & pass the dictionary object to that function, Waterfall step EndMainDlg gets called, received the Dictionary object via WaterfallStepContext object, based on the key read the value from the dictionary.
 
DialogSet code 
  1. private void ExecuteMainDialog(IStatePropertyAccessor<DialogState> dlgstate)  
  2. {  
  3.   
  4.     var waterFallSteps = new WaterfallStep[]  
  5.     {  
  6.         RunMainDialog,  
  7.         EndMainDlg  
  8.     };  
  9.   
  10.     _dialogSet = new DialogSet(dlgstate);  
  11.     _dialogSet.Add(new TextPrompt("text"));  
  12.     _dialogSet.Add(new NumberPrompt<int>("number"));  
  13.     _dialogSet.Add(new ChoicePrompt("choice"));  
  14.     _dialogSet.Add(new ExecuteChildDialog(nameof(UserForm), UserForm.PromptProperties()));  
  15.     _dialogSet.Add(new ExecuteChildDialog(nameof(PizzaOrder), PizzaOrder.PromptProperties()));  
  16.     _dialogSet.Add(new ExecuteChildDialog(nameof(RootDialog),RootDialog.PromptProperties()));  
  17.     _dialogSet.Add(new WaterfallDialog("main", waterFallSteps));  
  18.   
  19. }  
Output
 
 
Adding new property
 
Another advantage of a user-defined class is later if you want to add any new properties or a class based on that just insert into the proper place no need to do any change it will work.

Ex: Adding new property.

If you want to read the last name from the user, add LastName property in the User-defined class that’s it no need to do any changes,
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Threading.Tasks;  
  5. using MeetpupDialog.Model;  
  6.   
  7. namespace MeetpupDialog.ChildDialog  
  8. {  
  9.     public static class UserForm  
  10.     {  
  11.         public static List<Property> PromptProperties()  
  12.         {  
  13.             var lstproperty = new List<Property>  
  14.             {  
  15.                 new Property("text""FirstName""Please enter the First Name"),  
  16.                 new Property("text""LastName""Please enter the Last Name"),  
  17.                 new Property("number""moblieNo""Please enter the mobile Number")                  
  18.             };  
  19.   
  20.             return lstproperty;  
  21.         }  
  22.     }  
  23. }  
Output
 
 
You can find the complete sample here
 

Conclusion


I hope you understand how to implement the user-defined dialogs in Bot Framework V4.

Happy Reading. Happy coding.